Input Group
One field with icons, text, or buttons inside it, such as a search box or a comment composer.
When to use it
Use Input Group when something belongs inside the field: an icon that names it, a fixed prefix like https://, a unit, or a button that acts on what was typed.
If the extra part could stand on its own, such as a separate submit button, put it beside the field instead.
Input
One line.
Profile
The name shown in your workspace.
Used for account notifications.
Names, emails, and other short answers.
Input GroupThis page
A field with extras.
Search, prefixes, units, and inline actions.
Examples
12345678910111213141516171819import { AtSign, CircleCheck, Search } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"
<InputGroup> <InputGroupAddon> <Search aria-hidden="true" /> </InputGroupAddon> <InputGroupInput type="search" aria-label="Search" placeholder="Search" /></InputGroup>
<InputGroup> <InputGroupAddon> <AtSign aria-hidden="true" /> </InputGroupAddon> <InputGroupInput type="email" aria-label="Email" defaultValue="priya@example.com" /> <InputGroupAddon align="inline-end"> <CircleCheck aria-label="Verified" className="text-success" /> </InputGroupAddon></InputGroup>123456789101112131415161718192021import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group"
<InputGroup> <InputGroupAddon> <InputGroupText>https://</InputGroupText> </InputGroupAddon> <InputGroupInput aria-label="Website" placeholder="example" /> <InputGroupAddon align="inline-end"> <InputGroupText>.com</InputGroupText> </InputGroupAddon></InputGroup>
<InputGroup> <InputGroupAddon> <InputGroupText>$</InputGroupText> </InputGroupAddon> <InputGroupInput aria-label="Monthly budget" inputMode="decimal" placeholder="0.00" /> <InputGroupAddon align="inline-end"> <InputGroupText>USD</InputGroupText> </InputGroupAddon></InputGroup>1234567891011121314151617181920212223242526import { Check, Copy } from "lucide-react"import { ButtonIcon } from "@/components/ui/button"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
<InputGroup> <InputGroupInput readOnly aria-label="Invite link" value={link} /> <InputGroupAddon align="inline-end"> <Tooltip> <TooltipTrigger render={ <InputGroupButton size="icon-xs" aria-label={copied ? "Copied" : "Copy link"} onClick={() => navigator.clipboard.writeText(link).then(() => setCopied(true))} /> } > <ButtonIcon active={copied} activeIcon={<Check />}> <Copy /> </ButtonIcon> </TooltipTrigger> <TooltipContent>{copied ? "Copied" : "Copy link"}</TooltipContent> </Tooltip> </InputGroupAddon></InputGroup>12345678910111213141516import { Search } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"import { Kbd, KbdGroup } from "@/components/ui/kbd"
<InputGroup> <InputGroupAddon> <Search aria-hidden="true" /> </InputGroupAddon> <InputGroupInput type="search" aria-label="Search docs" placeholder="Search docs" /> <InputGroupAddon align="inline-end"> <KbdGroup> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </KbdGroup> </InputGroupAddon></InputGroup>12345678910111213141516171819202122232425262728import { ChevronDown } from "lucide-react"import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"
<InputGroup> <InputGroupInput type="search" aria-label="Search issues" placeholder="Search issues" /> <InputGroupAddon align="inline-end"> <DropdownMenu> <DropdownMenuTrigger render={<InputGroupButton />}> {scopes[scope]} <ChevronDown aria-hidden="true" data-icon="end" /> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuRadioGroup value={scope} onValueChange={setScope}> <DropdownMenuRadioItem value="all">All issues</DropdownMenuRadioItem> <DropdownMenuRadioItem value="mine">My issues</DropdownMenuRadioItem> <DropdownMenuRadioItem value="team">Team issues</DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuContent> </DropdownMenu> </InputGroupAddon></InputGroup>1234567891011121314151617181920212223import { Search } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group"import { Spinner } from "@/components/ui/spinner"
<InputGroup> <InputGroupAddon> <Search aria-hidden="true" /> </InputGroupAddon> <InputGroupInput type="search" aria-label="Search projects" defaultValue="Website" /> <InputGroupAddon align="inline-end"> <Spinner aria-label="Searching" /> </InputGroupAddon></InputGroup>
<InputGroup> <InputGroupInput aria-label="Username" defaultValue="priya-nair" /> <InputGroupAddon align="inline-end"> <InputGroupText> <Spinner aria-hidden="true" /> Checking… </InputGroupText> </InputGroupAddon></InputGroup>1234567891011121314151617181920212223242526import { ArrowUp } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupTextarea} from "@/components/ui/input-group"import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
<InputGroup> <InputGroupTextarea aria-label="Message" placeholder="Write a message…" value={message} onChange={onChange} /> <InputGroupAddon align="block-end"> <InputGroupText className="caption tabular-nums">{message.length}/500</InputGroupText> <Tooltip> <TooltipTrigger render={ <InputGroupButton variant="default" size="icon-sm" aria-label="Send" className="ml-auto" disabled={!message.trim()} /> } > <ArrowUp aria-hidden="true" /> </TooltipTrigger> <TooltipContent>Send</TooltipContent> </Tooltip> </InputGroupAddon></InputGroup>12345678import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"
<InputGroup> <InputGroupInput id="full-name" placeholder="Priya Nair" /> <InputGroupAddon align="block-start"> <label htmlFor="full-name" className="label-md text-foreground">Full name</label> </InputGroupAddon></InputGroup>How it behaves
InputGroup draws the field: its fill, hover edge, focus ring, and invalid state. Place InputGroupInput or InputGroupTextarea inside it; they drop their own surface.
InputGroupAddon sets align to inline-start, inline-end, block-start, or block-end. Block addons stack above or below the control, as in a comment box with actions underneath.
InputGroupButton uses Button's xs, sm, icon-xs, and icon-sm sizes and defaults to ghost. Give icon-only buttons an aria-label and a Tooltip.
Clicking an addon outside its buttons focuses the control. Label the control itself; the group is presentational and adds no behavior such as sending.
Put shortcut hints in an addon with Kbd, such as a KbdGroup for ⌘K at the end of a search field.
Build it
Add it with the shadcn CLI:
1pnpm dlx shadcn@latest add https://jzbui.com/r/input-group.jsonA complete example. Change the values to fit your product:
12345678910111213141516171819import { Paperclip, ArrowUp } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea} from "@/components/ui/input-group"
<InputGroup> <InputGroupTextarea aria-label="Comment" placeholder="Leave a comment…" /> <InputGroupAddon align="block-end" className="justify-end"> <InputGroupButton size="icon-sm" aria-label="Attach file"> <Paperclip aria-hidden="true" /> </InputGroupButton> <InputGroupButton variant="default" size="icon-sm" aria-label="Send comment"> <ArrowUp aria-hidden="true" /> </InputGroupButton> </InputGroupAddon></InputGroup>The full source is yours to edit once installed: