- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Card
- Checkbox
- Collapsible
- Combobox
- Context Menu
- Date Picker (Lite)
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP (Lite)
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
import { Search } from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupDemo() {
return (
<InputGroup class="max-w-xs">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
)
}Installation
pnpm dlx shadcnui-hono-jsx@latest add input-groupnpx shadcnui-hono-jsx@latest add input-groupyarn dlx shadcnui-hono-jsx@latest add input-groupbunx shadcnui-hono-jsx@latest add input-groupThis component uses a small client script. add installs it into public/shadcn/; serve that directory at /shadcn/ and load the script on pages that use the component (see Client Scripts):
<script type="module" src="/shadcn/input-group.js"></script>Usage
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>Composition
Use the following composition to build an InputGroup:
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupTextAlign
Use the align prop on InputGroupAddon to position the addon relative to the input.
For proper focus management, InputGroupAddon should always be placed after
InputGroupInput or InputGroupTextarea in the DOM. Use the align prop to
visually position the addon.
inline-start
Use align="inline-start" to position the addon at the start of the input. This is the default.
Icon positioned at the start.
import { SearchIcon } from "@/components/icons"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupInlineStart() {
return (
<Field class="max-w-sm">
<FieldLabel for="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon class="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
)
}inline-end
Use align="inline-end" to position the addon at the end of the input.
Icon positioned at the end.
import { EyeOffIcon } from "@/components/icons"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupInlineEnd() {
return (
<Field class="max-w-sm">
<FieldLabel for="inline-end-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput
id="inline-end-input"
type="password"
placeholder="Enter password"
/>
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the end.</FieldDescription>
</Field>
)
}block-start
Use align="block-start" to position the addon above the input.
Header positioned above the input.
Header positioned above the textarea.
import { CopyIcon, FileCodeIcon } from "@/components/icons"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export function InputGroupBlockStart() {
return (
<FieldGroup class="max-w-sm">
<Field>
<FieldLabel for="block-start-input">Input</FieldLabel>
<InputGroup class="h-auto">
<InputGroupInput
id="block-start-input"
placeholder="Enter your name"
/>
<InputGroupAddon align="block-start">
<InputGroupText>Full Name</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the input.</FieldDescription>
</Field>
<Field>
<FieldLabel for="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
class="font-mono text-sm"
/>
<InputGroupAddon align="block-start">
<FileCodeIcon class="text-muted-foreground" />
<InputGroupText class="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" class="ml-auto">
<CopyIcon />
<span class="sr-only">Copy</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Header positioned above the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}block-end
Use align="block-end" to position the addon below the input.
Footer positioned below the input.
Footer positioned below the textarea.
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export function InputGroupBlockEnd() {
return (
<FieldGroup class="max-w-sm">
<Field>
<FieldLabel for="block-end-input">Input</FieldLabel>
<InputGroup class="h-auto">
<InputGroupInput id="block-end-input" placeholder="Enter amount" />
<InputGroupAddon align="block-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the input.</FieldDescription>
</Field>
<Field>
<FieldLabel for="block-end-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-end-textarea"
placeholder="Write a comment..."
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton variant="default" size="sm" class="ml-auto">
Post
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Footer positioned below the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}Icon
import {
CheckIcon,
CreditCardIcon,
InfoIcon,
MailIcon,
SearchIcon,
StarIcon,
} from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupIcon() {
return (
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput type="email" placeholder="Enter your email" />
<InputGroupAddon>
<MailIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon>
<CreditCardIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<CheckIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon align="inline-end">
<StarIcon />
<InfoIcon />
</InputGroupAddon>
</InputGroup>
</div>
)
}Text
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export default function InputGroupTextExample() {
return (
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput placeholder="0.00" />
<InputGroupAddon align="inline-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupInput placeholder="example.com" class="pl-0.5!" />
<InputGroupAddon align="inline-end">
<InputGroupText>.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter your username" />
<InputGroupAddon align="inline-end">
<InputGroupText>@company.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Enter your message" />
<InputGroupAddon align="block-end">
<InputGroupText class="text-xs text-muted-foreground">
120 characters left
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
)
}Button
Your connection is not secure.
You should not enter any sensitive information on this site.
import { IconCopy, IconInfoCircle, IconStar } from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function InputGroupButtonExample() {
return (
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="https://x.com/shadcn" readOnly />
<InputGroupAddon align="inline-end">
<InputGroupButton aria-label="Copy" title="Copy" size="icon-xs">
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup class="[--radius:9999px]">
<Popover>
<PopoverTrigger render={<InputGroupAddon />}>
<InputGroupButton variant="secondary" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</PopoverTrigger>
<PopoverContent
align="start"
class="flex flex-col gap-1 rounded-xl text-sm"
>
<p class="font-medium">Your connection is not secure.</p>
<p>You should not enter any sensitive information on this site.</p>
</PopoverContent>
</Popover>
<InputGroupAddon class="pl-1.5 text-muted-foreground">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs">
<IconStar
data-favorite="false"
class="data-[favorite=true]:fill-blue-600 data-[favorite=true]:stroke-blue-600"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Type to search..." />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary">Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}Kbd
import { SearchIcon } from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Kbd } from "@/components/ui/kbd"
export function InputGroupKbd() {
return (
<InputGroup class="max-w-sm">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon class="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
)
}Dropdown
import { ChevronDownIcon, MoreHorizontal } from "@/components/icons"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupDropdown() {
return (
<div class="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput placeholder="Enter file name" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger
render={
<InputGroupButton
variant="ghost"
aria-label="More"
size="icon-xs"
/>
}
>
<MoreHorizontal />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}>
<DropdownMenuGroup>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
<DropdownMenuItem>Open location</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter search query" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger
render={
<InputGroupButton variant="ghost" class="pr-1.5! text-xs" />
}
>
Search In... <ChevronDownIcon class="size-3" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}>
<DropdownMenuGroup>
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Blog Posts</DropdownMenuItem>
<DropdownMenuItem>Changelog</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
</div>
)
}Spinner
import { LoaderIcon } from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
import { Spinner } from "@/components/ui/spinner"
export default function InputGroupSpinner() {
return (
<div class="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput placeholder="Searching..." />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Processing..." />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Saving changes..." />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Refreshing data..." />
<InputGroupAddon>
<LoaderIcon class="animate-spin" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText class="text-muted-foreground">
Please wait...
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
)
}Textarea
import {
IconBrandJavascript,
IconCopy,
IconCornerDownLeft,
IconRefresh,
} from "@/components/icons"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export default function InputGroupTextareaExample() {
return (
<div class="grid w-full max-w-md gap-4">
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
class="min-h-[200px]"
/>
<InputGroupAddon align="block-end" class="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" class="ml-auto" variant="default">
Run <IconCornerDownLeft />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" class="border-b">
<InputGroupText class="font-mono font-medium">
<IconBrandJavascript />
script.js
</InputGroupText>
<InputGroupButton class="ml-auto" size="icon-xs">
<IconRefresh />
</InputGroupButton>
<InputGroupButton variant="ghost" size="icon-xs">
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}Custom Input
Add the data-slot="input-group-control" attribute to your custom input for automatic focus state handling.
Here's an example of a custom resizable textarea from a third-party library.
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
} from "@/components/ui/input-group"
export default function InputGroupCustom() {
return (
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<textarea
data-slot="input-group-control"
class="flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base transition-[color,box-shadow] outline-none md:text-sm"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton class="ml-auto" size="sm" variant="default">
Submit
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}RTL
To enable RTL support in shadcn/ui, see the RTL configuration guide.
Footer positioned below the textarea.
تذييل موضع أسفل منطقة النص.
כותרת תחתונה ממוקמת מתחת לאזור הטקסט.
// This example sets dir and uses useTranslation because this site is not RTL.
// In an RTL app you won't need them; see the RTL guide.
import { Search } from "@/components/icons"
import {
type Translations,
useTranslation,
} from "@/components/language-selector"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
import { Spinner } from "@/components/ui/spinner"
const translations: Translations = {
en: {
dir: "ltr",
values: {
placeholder: "Search...",
results: "12 results",
searching: "Searching...",
saving: "Saving...",
savingChanges: "Saving changes...",
textareaLabel: "Textarea",
textareaPlaceholder: "Write a comment...",
characterCount: "0/280",
post: "Post",
textareaDescription: "Footer positioned below the textarea.",
},
},
ar: {
dir: "rtl",
values: {
placeholder: "بحث...",
results: "١٢ نتيجة",
searching: "جاري البحث...",
saving: "جاري الحفظ...",
savingChanges: "جاري حفظ التغييرات...",
textareaLabel: "منطقة النص",
textareaPlaceholder: "اكتب تعليقًا...",
characterCount: "٠/٢٨٠",
post: "نشر",
textareaDescription: "تذييل موضع أسفل منطقة النص.",
},
},
he: {
dir: "rtl",
values: {
placeholder: "חפש...",
results: "12 תוצאות",
searching: "מחפש...",
saving: "שומר...",
savingChanges: "שומר שינויים...",
textareaLabel: "אזור טקסט",
textareaPlaceholder: "כתוב תגובה...",
characterCount: "0/280",
post: "פרסם",
textareaDescription: "כותרת תחתונה ממוקמת מתחת לאזור הטקסט.",
},
},
}
export function InputGroupRtl() {
const { dir, t } = useTranslation(translations, "ar")
return (
<div class="grid w-full max-w-sm gap-6">
<InputGroup class="max-w-xs">
<InputGroupInput placeholder={t.placeholder} />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">{t.results}</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder={t.searching} />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder={t.savingChanges} />
<InputGroupAddon align="inline-end">
<InputGroupText>{t.saving}</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<FieldGroup class="max-w-sm">
<Field>
<FieldLabel for="rtl-textarea">{t.textareaLabel}</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="rtl-textarea"
placeholder={t.textareaPlaceholder}
/>
<InputGroupAddon align="block-end">
<InputGroupText>{t.characterCount}</InputGroupText>
<InputGroupButton variant="default" size="sm" class="ms-auto">
{t.post}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>{t.textareaDescription}</FieldDescription>
</Field>
</FieldGroup>
</div>
)
}Notes
- Accepts
classinstead ofclassName. - Clicking an addon focuses the input with the client script
/shadcn/input-group.js(<script type="module" src="/shadcn/input-group.js">); without it the addon is not clickable. - Upstream documentation: shadcn/ui Input Group. See also Differences from shadcn/ui.
API Reference
InputGroup
The main component that wraps inputs and addons.
| Prop | Type | Default |
|---|---|---|
className | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon
Displays icons, text, buttons, or other content alongside inputs.
For proper focus navigation, the InputGroupAddon component should be placed
after the input. Set the align prop to position the addon.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
className | string |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>For <InputGroupInput />, use the inline-start or inline-end alignment. For <InputGroupTextarea />, use the block-start or block-end alignment.
The InputGroupAddon component can have multiple InputGroupButton components and icons.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>InputGroupButton
Displays buttons within input groups.
| Prop | Type | Default |
|---|---|---|
size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" |
variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" |
className | string |
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton size="icon-xs" aria-label="Copy">
<CopyIcon />
</InputGroupButton>InputGroupInput
Replacement for <Input /> when building input groups. This component has the input group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.
| Prop | Type | Default |
|---|---|---|
className | string |
All other props are passed through to the underlying <Input /> component.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea
Replacement for <Textarea /> when building input groups. This component has the textarea group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.
| Prop | Type | Default |
|---|---|---|
className | string |
All other props are passed through to the underlying <Textarea /> component.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>