Input Group
Add addons, buttons, or helper content alongside an input or textarea.
Last updated August 7, 2026
Copied from shadcn/ui.
Overview
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupDemoExample() {
return (
<InputGroup className="max-w-xs">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
)
}Installation
$ npx shadcn@latest add https://ui.persian-labs.ir/r/input-group.jsonUsage
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export function Example() {
return (
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
)
}Composition
Use the following composition to build an InputGroup:
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupTextAlign
For proper focus management, InputGroupAddon should always be placed after InputGroupInput or InputGroupTextarea in the DOM. Use the align prop on InputGroupAddon to position the addon relative to the input.
Inline start
Icon positioned at the start.
import { SearchIcon } from "lucide-react"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupInlineStartExample() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
)
}Inline end
Icon positioned at the end.
import { EyeOffIcon } from "lucide-react"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupInlineEndExample() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="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
Header positioned above the input.
Header positioned above the textarea.
import { CopyIcon, FileCodeIcon } from "lucide-react"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export function InputGroupBlockStartExample() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-start-input">Input</FieldLabel>
<InputGroup className="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 htmlFor="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
className="font-mono text-sm"
/>
<InputGroupAddon align="block-start">
<FileCodeIcon className="text-muted-foreground" />
<InputGroupText className="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" className="ms-auto">
<CopyIcon />
<span className="sr-only">Copy</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Header positioned above the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}Block end
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 InputGroupBlockEndExample() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-end-input">Input</FieldLabel>
<InputGroup className="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 htmlFor="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" className="ms-auto">
Post
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Footer positioned below the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}Icon
import {
CheckIcon,
CreditCardIcon,
InfoIcon,
MailIcon,
SearchIcon,
StarIcon,
} from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupIconExample() {
return (
<div className="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 function InputGroupTextExample() {
return (
<div className="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" className="ps-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 className="text-xs text-muted-foreground">
120 characters left
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
)
}Button
"use client"
import * as React from "react"
import { CheckIcon, CopyIcon, InfoIcon, StarIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export function InputGroupButtonExample() {
const [isCopied, setIsCopied] = React.useState(false)
const [isFavorite, setIsFavorite] = React.useState(false)
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="https://x.com/persianlabsui" readOnly />
<InputGroupAddon align="inline-end">
<InputGroupButton
aria-label="Copy"
title="Copy"
size="icon-xs"
onClick={() => {
navigator.clipboard.writeText("https://x.com/persianlabsui")
setIsCopied(true)
setTimeout(() => setIsCopied(false), 1500)
}}
>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup className="[--radius:9999px]">
<Popover>
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
<InputGroupButton variant="secondary" size="icon-xs">
<InfoIcon />
</InputGroupButton>
</PopoverTrigger>
<PopoverContent
align="start"
className="flex flex-col gap-1 rounded-xl text-sm"
>
<p className="font-medium">Your connection is not secure.</p>
<p>
You should not enter any sensitive information on this site.
</p>
</PopoverContent>
</Popover>
<InputGroupAddon className="ps-1.5 text-muted-foreground">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton
onClick={() => setIsFavorite(!isFavorite)}
size="icon-xs"
>
<StarIcon
data-favorite={isFavorite}
className="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 "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Kbd } from "@/components/ui/kbd"
export function InputGroupKbdExample() {
return (
<InputGroup className="max-w-xs">
<InputGroupInput placeholder="Search components" />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
)
}Dropdown
"use client"
import { ChevronDownIcon, MoreHorizontalIcon } from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
export function InputGroupDropdownExample() {
return (
<div className="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"
/>
}
>
<MoreHorizontalIcon />
</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"
className="pe-1.5! text-xs"
/>
}
>
Search In... <ChevronDownIcon className="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 "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
import { Spinner } from "@/components/ui/spinner"
export function InputGroupSpinnerExample() {
return (
<div className="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 className="animate-spin" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText className="text-muted-foreground">
Please wait...
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
)
}Textarea
import { CopyIcon, CornerDownLeftIcon, FileCodeIcon, RefreshCwIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
export function InputGroupTextareaExample() {
return (
<div className="grid w-full max-w-md gap-4">
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
className="min-h-[200px]"
/>
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" className="ms-auto" variant="default">
Run <CornerDownLeftIcon />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<FileCodeIcon />
script.js
</InputGroupText>
<InputGroupButton className="ms-auto" size="icon-xs">
<RefreshCwIcon />
</InputGroupButton>
<InputGroupButton variant="ghost" size="icon-xs">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}Custom Input
Add the data-slot="input-group-control" attribute to any custom input for automatic focus-state styling. Here's a custom auto-resizing textarea from a third-party library.
"use client"
import TextareaAutosize from "react-textarea-autosize"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
} from "@/components/ui/input-group"
export function InputGroupCustomExample() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<TextareaAutosize
data-slot="input-group-control"
className="field-sizing-content flex min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base outline-none transition-[color,box-shadow] md:text-sm"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton className="ms-auto" size="sm" variant="default">
Submit
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}RTL
پابرگ زیر متن قرار گرفته است.
import { SearchIcon } from "lucide-react"
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"
export function InputGroupRtlExample() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup className="max-w-xs">
<InputGroupInput placeholder="جستجو..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">۱۲ نتیجه</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="در حال ذخیره تغییرات..." />
<InputGroupAddon align="inline-end">
<InputGroupText>در حال ذخیره...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="rtl-textarea">متن</FieldLabel>
<InputGroup>
<InputGroupTextarea id="rtl-textarea" placeholder="نظر خود را بنویسید..." />
<InputGroupAddon align="block-end">
<InputGroupText>۰/۲۸۰</InputGroupText>
<InputGroupButton variant="default" size="sm" className="ms-auto">
ارسال
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>پابرگ زیر متن قرار گرفته است.</FieldDescription>
</Field>
</FieldGroup>
</div>
)
}API Reference
InputGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | Additional classes for the group's container. |
InputGroupAddon
| Prop | Type | Default | Description |
|---|---|---|---|
| align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" | Positions the addon relative to the input. |
InputGroupButton
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" | The size of the button. |
| variant | ButtonVariant | "ghost" | The visual style of the button. |
InputGroupInput
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | Additional classes for the input. |
InputGroupTextarea
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | Additional classes for the textarea. |