Input group
An input with icons, text, buttons or a keyboard hint attached, sharing one border and focus ring.
Used in Pro blocks: Diff Review, Billing, Models, and 4 more
pnpm dlx shadcn@latest add https://hextaui.com/r/input-group.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopy and paste the following code into your project.
components/ui/input-group.tsx components/ui/input.tsx components/ui/button.tsx components/ui/number-flow.tsx lib/motion.ts Update the import paths to match your project setup.
Put <InputGroupInput /> or <InputGroupTextarea /> first and the addons after it. Addons place themselves with align, so the field comes first in the tab order and for screen readers.
The group tracks its field, so the smart parts need no wiring. <InputGroupClear />, <InputGroupPasswordToggle /> and <InputGroupCount /> read the value, type and maxLength from the field, whether it's controlled or not.
Icons sit inside the border on either side. Clicking an icon focuses the input, so the whole group feels like one field.
<InputGroupClear /> fades in once there's a value. It clears through the browser's edit history, so Cmd+Z brings the text back, and it fires your onChange. Escape clears too. A second Escape is left for the dialog or popover around it.
<InputGroupCount /> counts against the field's maxLength. Only the digits that change roll. The count darkens near the limit and turns red at it, and a typed key past the limit nudges it. Screen readers hear a message when the field gets close to the limit and when it reaches it, never on every key.
Use <InputGroupText /> for units, currencies and URL parts. The input's padding shrinks next to an addon so the text reads as one value.
<InputGroupButton /> is a ghost button sized to fit inside the field. Its corners are concentric with the group's, and it gets its own focus ring.
A plain <kbd> inside an addon is styled as a key cap. It's a visual hint only, so bind the shortcut yourself.
<InputGroupTextarea /> grows with its content up to 16rem, then scrolls. The height eases between lines instead of jumping. A block-end addon becomes a toolbar under it, and buttons at its edges get the same inset as the corner they sit in.
A block-start addon sits above the field. Add separator to draw a hairline between them.
<InputGroupPasswordToggle /> switches a type="password" field to text and back. The caret and selection stay where they were, a mouse click keeps focus in the field, and the password is hidden again when the form submits. Control it with revealed.
size on the group sets the height and passes down to the input, matching <Input /> sizes. Buttons keep concentric corners at every size.
Set aria-invalid on the input and the whole group turns red, including its focus ring. Link the message with aria-describedby.
A disabled input dims the whole group and shows a not-allowed cursor over it. Disable addon buttons too, since they stay usable otherwise.
Inline addons ease to their new width when their content changes, so the field never jumps as a spinner turns into a result count. The spinner only turns when motion is allowed, and role="status" announces the text.
Render an <InputGroupButton /> as a dropdown trigger to scope the input.
Long values scroll inside the input instead of stretching the group. Wrap long addon text in a truncating span with a max width.
Addons, padding and corner radii use logical sides, so inline-start lands on the right.
| Key | Action |
|---|---|
| Tab | Moves from the field to each addon button, in source order. |
| ShiftTab | Moves back through the buttons and the field. |
| Escape | With an InputGroupClear, clears the field. When it's already empty, Escape passes through. |
- Every field needs a name. Use a visible label, a Field, or
aria-label. Icons and addon text aren't part of the field's name. - Give icon-only buttons an
aria-label. - When addon text carries meaning, like a currency or a domain, add it to the label or reference it with
aria-describedby. <InputGroupClear />is skipped in the tab order because Escape does the same thing. The password toggle stays tabbable and keeps the same name, witharia-pressedreporting its state.- When a submit finds the field invalid, the group shakes once. With reduced motion the red border is the only cue.
- On touch screens the field text is at least 16px so phones don't zoom in when it's focused.
<InputGroupInput /> and <InputGroupTextarea /> accept the props of the elements they render. The other parts accept their element's attributes.
| Prop | Type | Default |
|---|---|---|
sizeHeight of the group, passed down to the input. | "sm" | "default" | "lg" | "default" |
| Attribute | Description |
|---|---|
data-slot="input-group" | Target the group in CSS. Renders role="group". |
data-size | The current size. |
data-filled | Present while the field has a value. |
data-shake | Present while the group shakes after a failed submit. |
data-disabled | Set it yourself to dim the group when only the addons are disabled. |
--input-group-radius | Corner radius of the group. Buttons and key caps derive their radius from it. |
--input-group-height | Height of the group for the current size. |
| Prop | Type | Default |
|---|---|---|
sizeInherited from the group. | "sm" | "default" | "lg" | – |
aria-invalid | boolean | – |
disabled | boolean | false |
readOnly | boolean | false |
| Attribute | Description |
|---|---|
data-slot="input-group-control" | Marks the field. The group reads its focus, invalid, disabled and read-only states from it. |
data-invalid | Present when a surrounding Field marks the value invalid. |
data-disabled | Present when the field is disabled. |
data-focused | Present while the field has focus. |
data-filled | Present when the field has a value. |
data-dirty | Present once the value differs from the initial one. |
data-touched | Present once the field has been focused and left. |
| Prop | Type | Default |
|---|---|---|
autoResizeGrow with the content up to 16rem, easing between heights. | boolean | true |
shakeShake the group when a submit finds it invalid. | boolean | true |
rows | number | – |
aria-invalid | boolean | – |
disabled | boolean | false |
| Attribute | Description |
|---|---|
data-slot="input-group-control" | Marks the field. The group reads its focus, invalid, disabled and read-only states from it. |
data-invalid | Present when a surrounding Field marks the value invalid. |
data-disabled | Present when the field is disabled. |
data-focused | Present while the field has focus. |
data-filled | Present when the field has a value. |
data-dirty | Present once the value differs from the initial one. |
data-touched | Present once the field has been focused and left. |
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
separatorDraw a hairline between a block addon and the field. | boolean | false |
| Attribute | Description |
|---|---|
data-slot="input-group-addon" | Target addons in CSS. |
data-align | The current alignment. |
data-separator | Present when separator is set. |
--input-group-addon-inset | Space between the group's edge and a button or key cap inside it. |
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "secondary" | "ghost" | "destructive" | "link" | "ghost" |
size | "xs" | "sm" | "icon-xs" | "icon-sm" | "xs" |
type | string | "button" |
feedbackEvery Button prop works, including the loading and success flow. | boolean | false |
| Attribute | Description |
|---|---|
data-slot="input-group-button" | Target addon buttons in CSS. |
data-size | The current size. |
| Prop | Type | Default |
|---|---|---|
onClearCalled after the field is cleared. | () => void | – |
aria-label | string | "Clear" |
children | ReactNode | <IconX /> |
| Attribute | Description |
|---|---|
data-slot="input-group-clear" | Target the clear button in CSS. |
data-visible | Present while the field has a value and is editable. |
| Prop | Type | Default |
|---|---|---|
revealedControlled state. Leave unset to let it manage itself. | boolean | – |
onRevealedChange | (revealed: boolean) => void | – |
aria-label | string | "Show password" |
| Attribute | Description |
|---|---|
data-slot="input-group-password-toggle" | Target the toggle in CSS. |
data-revealed | Present while the password is shown. |
| Prop | Type | Default |
|---|---|---|
thresholdCharacters left when the count starts to stand out. | number | 10% of maxLength, at most 20 |
announcementMessage for screen readers when the count crosses the threshold or hits the limit. | (remaining: number) => string | – |
| Attribute | Description |
|---|---|
data-slot="input-group-count" | Target the count in CSS. |
data-state="near" | "limit" | Present within the threshold, and when no characters are left. |
data-bump | Present briefly when a key is pressed at the limit. |
| Attribute | Description |
|---|---|
data-slot="input-group-text" | Target addon text in CSS. |
- ButtonButtons in every variant and size, with a built-in loading, success and error flow that skips the spinner for fast requests.
- InputA text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.
- MotionThe easing curves, durations and reduced-motion check every component animates with, plus hooks for size morphs and sliding highlights.
- useAutosizeGrows a textarea with what you write between its minimum and maximum height, animating each change without ever touching the text.
- useComposedRefKeeps a ref to your own element while still forwarding it to whatever ref the parent passed in.
- useInvalidShakeShakes a form control when a submit attempt finds it invalid, and never while someone is still typing.
Used in Pro blocks
Blocks that build on Input group.
- Diff ReviewA review of an agent’s file edits before they land. Handles accept or reject per change, comments back to the agent and edits still streaming.
- BillingThe plan and usage page of an AI product. Handles credits running out mid-cycle, plan changes with proration and card errors.
- ModelsThe Models page of an AI product’s settings. Handles the default model and effort, providers and custom servers with a connection test.
- ProfileThe Profile section of an AI product’s settings. Handles photo cropping, a username checked as you type and email changes confirmed by code.
- SecurityThe Security section of an AI product’s settings. Handles sessions, two-factor with recovery codes and deletion behind a typed confirmation.
- SettingsThe settings page of an AI product. Handles a save bar that appears only when something changed, field errors and loading states.
Every block comes with HextaUI Pro, and Prompt Input is free.