InputGroup
Combines an input with addons like icons, buttons, or text labels. Uses role="group" with a shared border container and flexible addon positioning via align prop.
Anatomy
An <InputGroup> container wraps an <InputGroupInput> and one or more <InputGroupAddon> elements. Addons position themselves via the align prop: inline-start, inline-end, block-start, or block-end.
role="group"data-slot="input-group"
Addon Positions
Addons can be positioned at any edge of the input group.
inline-start (leading icon)
inline-end (trailing icon)
Both sides
Sub-components
Beyond addons and inputs, the group also supports buttons and text elements.
With button
With text addon
https://
States
Disabled
Invalid
Valid
Design Guidelines
Do
- Use icons to clarify input purpose. A search icon, mail icon, or currency symbol helps users understand the expected input.
- Keep addons lightweight. Addons should support the input, not compete with it. Icons and short text work best.
- Use InputGroupButton for actions. Actions like copy, toggle visibility, or submit should use the dedicated button sub-component.
Don't
- Don't overload with multiple addons. More than two addons creates visual clutter.
- Don't use decorative-only icons. Every addon should convey meaning or enable an action.
- Don't nest input groups. Each group should be a single level of composition.
Developer Reference
Accessibility
- The container uses
role="group"to communicate the grouping relationship. - Addons with
align="inline-start"use absolute positioning — the input's padding is adjusted automatically. - InputGroupButton defaults to
type="button"to prevent accidental form submission. - The group supports both InputGroupInput and InputGroupTextarea for multi-line addons.
- Pass
data-valid="true"on InputGroup for a success-green border and ring; passaria-invalidon the inner input for the destructive state.
Usage
import {
InputGroup, InputGroupAddon, InputGroupInput,
InputGroupButton, InputGroupText
} from "@/components/ui/input-group"
import { Mail, Search, CheckCircle2 } from "lucide-react"
// Leading icon
<InputGroup>
<InputGroupAddon align="inline-start">
<Mail className="size-4 text-muted-foreground" />
</InputGroupAddon>
<InputGroupInput placeholder="Email" />
</InputGroup>
// Trailing icon
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon align="inline-end">
<Search className="size-4 text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
// Invalid state (destructive ring)
<InputGroup>
<InputGroupAddon align="inline-start">
<Mail className="size-4 text-muted-foreground" />
</InputGroupAddon>
<InputGroupInput placeholder="Email" aria-invalid />
</InputGroup>
// Valid state (success ring)
<InputGroup data-valid={true}>
<InputGroupAddon align="inline-start">
<CheckCircle2 className="size-4 text-success" />
</InputGroupAddon>
<InputGroupInput placeholder="Email" defaultValue="hello@example.com" />
</InputGroup>
// With text prefix (padded)
<InputGroup>
<InputGroupText>https://</InputGroupText>
<InputGroupInput placeholder="example.com" />
</InputGroup>