Input Group
Joins several controls, a field, a Select, a static affix, a small icon-only action, into one seamless segmented shell. The group owns the border and focus ring; each segment goes chromeless and melts in.
Installation#
Usage#
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
import { InputRoot, InputField } from "@/components/ui/input"
export function Example() {
return (
<InputGroup>
<InputGroupAddon>https://</InputGroupAddon>
<InputRoot>
<InputField placeholder="yourcompany" />
</InputRoot>
<InputGroupAddon>.com</InputGroupAddon>
</InputGroup>
)
}When to reach for it#
An Input already handles adornments inside a single field - a leading icon (InputPrefix), a divided label (InputPrefixLabel), a trailing icon button (InputSuffixButton). Reach for InputGroup only when you need to join several distinct controls - a Select, another field, a static affix - into one unit. One shell, one focus ring, one set of rounded corners.
A prominent text action - Subscribe, Invite, Search - does not belong inside the shell. Crammed in, it reads as a button trapped in a field. Keep it a detached Button beside the group instead (see below). Only a small icon-only action may melt in.
Pairing with an action#
Do not fuse a big text button into the field. Drop the group (or a plain InputRoot) and the Button into one flex row (items-stretch gap-2) at the same size: the field flexes, the button caps the row from outside, and the heights line up on their own. It reads as a real button, not a segment.
With a Select#
Drop a real Select in as a segment and it melts into the shell - its own border, ring, and background are stripped while its menu, keyboard nav, and ARIA stay intact. The field flexes to fill the row. The Invite action, being a text button, stays detached beside the group - same rule as the newsletter, one step richer.
Text affixes#
InputGroupAddon is a static, non-interactive segment for a unit, protocol, or symbol. It shares the shell background and reads as a muted label, on either side of the field.
Icon-only actions can melt in#
The one action that may live inside the shell is a small icon-only button - copy, clear, a submit arrow. With no label to crowd the field, it sits flush as a fused segment: it keeps its fill and hover but drops its own radius, ring, and press-scale. Use iconOnly and give it an aria-label.
Sizes#
The size prop sets the shell height (sm 32, md 36, lg 40px) and the affix scale. Give the inner controls - and the detached Button - the matching size so every part lines up.
States#
hasError paints the whole shell red and turns the focus ring destructive; disabled dims and locks the row. Inside a Field, both cascade automatically. For form semantics, also disable the inner control so it is excluded from submission.
API reference#
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Shell height + affix sizing. Match it on the inner controls. |
| hasError | boolean | - | Error styling. Cascades from a surrounding Field. |
| disabled | boolean | - | Dim and lock the shell. Cascades from a surrounding Field. |
| ...divProps | ComponentProps<"div"> | - | Forwarded to the shell. className merges last. |
InputGroupAddon is a plain span segment and accepts every native span prop plus className.