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

USD

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; pass aria-invalid on 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>