Input Group
A flexible component for grouping inputs with addons, buttons, and other elements.
import { SearchIcon } from "lucide-react";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput aria-label="Search" placeholder="Search" type="search" />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
);
}
Installation
pnpm dlx cnippet@latest add input-group
Usage
import { Input } from "@/components/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput type="email" placeholder="Email" />
<InputGroupAddon>
<MailIcon />
</InputGroupAddon>
</InputGroup>Examples
Variants With text
With Start Text
With End Text
With Start and End Text
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group";
export default function Particle() {
return (
<div className="space-y-4">
<div className="space-y-2 [&_p]:text-xs">
<p>With Start Text</p>
<InputGroup>
<InputGroupInput
aria-label="Set your URL"
className="*:[input]:ps-0!"
placeholder="cnippet"
type="search"
/>
<InputGroupAddon>
<InputGroupText>i.cnippet.dev/</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
<div className="space-y-2 [&_p]:text-xs">
<p>With End Text</p>
<InputGroup>
<InputGroupInput
aria-label="Choose a username"
placeholder="Choose a username"
type="text"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>@cnipept.dev</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
<div className="space-y-2 [&_p]:text-xs">
<p>With Start and End Text</p>
<InputGroup>
<InputGroupInput
aria-label="Enter your domain"
className="*:[input]:px-0!"
placeholder="cnippet"
type="text"
/>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText>.dev</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
</div>
);
}
With Number Field
import {
InputGroup,
InputGroupAddon,
InputGroupText,
} from "@/components/ui/input-group";
import { NumberField, NumberFieldInput } from "@/components/ui/number-field";
export default function Particle() {
return (
<InputGroup>
<NumberField aria-label="Enter the amount" defaultValue={100}>
<NumberFieldInput className="text-left" />
</NumberField>
<InputGroupAddon>
<InputGroupText>₹</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText>INR</InputGroupText>
</InputGroupAddon>
</InputGroup>
);
}
With Tooltip
"use client";
import { InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput
aria-label="Password"
placeholder="Password"
type="password"
/>
<InputGroupAddon align="inline-end">
<Popover>
<PopoverTrigger
openOnHover
render={
<Button
aria-label="Password requirements"
size="icon-xs"
variant="ghost"
/>
}
>
<InfoIcon />
</PopoverTrigger>
<PopoverPopup side="top" tooltipStyle>
<p>Min. 8 characters</p>
</PopoverPopup>
</Popover>
</InputGroupAddon>
</InputGroup>
);
}
With Icon Button
"use client";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useRef } from "react";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Particle() {
const { copyToClipboard, isCopied } = useCopyToClipboard();
const inputRef = useRef<HTMLInputElement>(null);
return (
<InputGroup>
<InputGroupInput
aria-label="Url"
defaultValue="https://ui.cnippet.dev"
ref={inputRef}
type="text"
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<Button
aria-label="Copy"
onClick={() => {
if (inputRef.current) {
copyToClipboard(inputRef.current.value);
}
}}
size="icon-xs"
variant="ghost"
/>
}
>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</TooltipTrigger>
<TooltipPopup>
<p>Copy to clipboard</p>
</TooltipPopup>
</Tooltip>
</InputGroupAddon>
</InputGroup>
);
}
With Button
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput placeholder="Type to search…" type="search" />
<InputGroupAddon align="inline-end">
<Button size="xs" variant="secondary">
Search
</Button>
</InputGroupAddon>
</InputGroup>
);
}
With Badge
import { Badge } from "@/components/ui/badge";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput placeholder="Type to search…" type="search" />
<InputGroupAddon align="inline-end">
<Badge variant="info">Badge</Badge>
</InputGroupAddon>
</InputGroup>
);
}
With Keyboard Shortcut
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Kbd } from "@/components/ui/kbd";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput placeholder="Search…" type="search" />
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
);
}
With Inner Label
"use client";
import { InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput id="email-1" placeholder="team@coss.com" type="email" />
<InputGroupAddon align="block-start">
<Label className="text-foreground" htmlFor="email-1">
Email
</Label>
<Popover>
<PopoverTrigger
className="ml-auto"
openOnHover
render={<Button className="-m-1" size="icon-xs" variant="ghost" />}
>
<InfoIcon />
</PopoverTrigger>
<PopoverPopup side="top" tooltipStyle>
<p>We'll use this to send you notifications</p>
</PopoverPopup>
</Popover>
</InputGroupAddon>
</InputGroup>
);
}
Sizes
Small
Large
import { SearchIcon } from "lucide-react";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
export default function Particle() {
return (
<div className="space-y-4">
<div className="flex items-center justify-between gap-10 [&_p]:text-xs">
<p>Small</p>
<InputGroup>
<InputGroupInput
aria-label="Search"
placeholder="Search"
size="sm"
type="search"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
</div>
<div className="flex items-center justify-between gap-10 [&_p]:text-xs">
<p>Large</p>
<InputGroup>
<InputGroupInput
aria-label="Search"
placeholder="Search"
size="lg"
type="search"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
</div>
</div>
);
}
Loading
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Spinner } from "@/components/ui/spinner";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput disabled placeholder="Searching…" type="search" />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
);
}
With Textarea
"use client";
import { ArrowUpIcon, PlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group";
import {
Menu,
MenuItem,
MenuPopup,
MenuTrigger,
} from "@/components/ui/menu";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Particle() {
return (
<InputGroup className="w-md">
<InputGroupTextarea placeholder="Ask, Search or Chat…" />
<InputGroupAddon align="block-end">
<Menu>
<Tooltip>
<TooltipTrigger
render={
<MenuTrigger
render={
<Button
aria-label="Add files"
className="rounded-full"
size="icon-sm"
variant="ghost"
/>
}
>
<PlusIcon />
</MenuTrigger>
}
/>
<TooltipPopup>Add files and more</TooltipPopup>
</Tooltip>
<MenuPopup align="start">
<MenuItem>Add photos & files</MenuItem>
<MenuItem>Create image</MenuItem>
<MenuItem>Thinking</MenuItem>
<MenuItem>Deep research</MenuItem>
</MenuPopup>
</Menu>
<InputGroupText className="ml-auto">78% used</InputGroupText>
<Tooltip>
<TooltipTrigger
render={
<Button
aria-label="Send"
className="rounded-full"
size="icon-sm"
variant="default"
>
<ArrowUpIcon />
</Button>
}
/>
<TooltipPopup>Send</TooltipPopup>
</Tooltip>
</InputGroupAddon>
</InputGroup>
);
}
API Reference
InputGroup
The main component that wraps inputs and addons.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | React.ComponentProps<'div'> | All standard div attributes are supported |
InputGroupAddon
A container for addons like icons, text, buttons, and other elements. Can be positioned at the start or end (inline), or top or bottom (block) of the input.
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'inline-start' | 'inline-end' | 'block-start' | 'block-end' | 'inline-start' | The position of the addon relative to the input. Use inline-start or inline-end for inputs, and block-start or block-end for textareas |
className | string | Additional CSS classes to apply to the component | |
...props | React.ComponentProps<'div'> | All standard div attributes are supported |
For proper focus navigation, the InputGroupAddon component should be placed
after the input in the DOM order.
InputGroupText
A text container component for displaying text content within an InputGroupAddon. Automatically styles text with muted foreground color and handles icon sizing.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | React.ComponentProps<'span'> | All standard span attributes are supported |
InputGroupInput
A specialized input component for use within InputGroup. It's essentially an unstyled Input component that inherits styling from the parent InputGroup.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | InputProps | All props from the Input component are supported, including type, placeholder, disabled, etc. |
InputGroupTextarea
A specialized textarea component for use within InputGroup. It's essentially an unstyled Textarea component that inherits styling from the parent InputGroup.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | TextareaProps | All props from the Textarea component are supported, including placeholder, disabled, rows, etc. |