Skip to content
Component installs need the registry setup
VegaStack Design

Input Group

An input or textarea with addons — icons, text, buttons, kbd hints, spinners and dropdowns, aligned inline or block on one bordered surface.

Status
stable
Since
0.10.0
Accessibility pattern
native input with labelled addons

Last updated

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Input Group before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify input-group

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/input-group

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

The same command installs the registry items it composes: @vegastack/button, @vegastack/input, @vegastack/textarea.

Usage

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group";

<InputGroup>
  <InputGroupInput placeholder="Search..." />
  <InputGroupAddon>
    <SearchIcon />
  </InputGroupAddon>
</InputGroup>;

Anatomy

InputGroup — data-slot="input-group"
InputGroupAddon — data-slot="input-group-addon"
InputGroupButton
InputGroupText
InputGroupInput — data-slot="input-group-control"
InputGroupTextarea — data-slot="input-group-control"

Examples

Composition

InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupText
$

Align

align on InputGroupAddon takes inline-start (the default), inline-end, block-start and block-end. Put the addon after the control in the DOM whatever the alignment — the focus handling reads document order, and align only moves it visually.

The addon sits before the control.

The addon sits after the control.

script.js

A header above the control.

0/280

A footer below the control.

Icon

An icon addon sits on either edge, or on both.

Text

InputGroupText carries a prefix, a suffix or a counter.

$
USD
https://
.com
120 characters left

Button

InputGroupButton is a Button sized for the field's interior. Give an icon-only one an aria-label.

https://

Kbd

A Kbd addon advertises the shortcut that focuses the field.

⌘K

A DropdownMenu trigger rendered as an InputGroupButton.

Spinner

A Spinner addon reports work in flight without moving the control.

Saving…

Textarea

InputGroupTextarea takes a header and a footer at once.

script.js
Line 1, Column 1

Textarea with an inline addon

Beside a textarea, an inline-start or inline-end addon sits at the top, level with the first line, rather than at the textarea's middle — the corner a status icon such as AutoSaveIndicator variant="icon" belongs in (FRM-16).

Custom Input

Put data-slot="input-group-control" on any control and the group picks up its focus and invalid state.

RTL

Addon order is order-first/order-last over logical padding, so a right-to-left document mirrors the whole group.

States

The group's rest, invalid and disabled chrome. Click or Tab into a field to see the focus state: the group's border darkens subtly (border-ring/50) over 150ms and nothing fills; the invalid group keeps its destructive border while focused.

API Reference

PropTypeDefaultDescription
align"block-end" | "block-start" | "inline-end" | "inline-start" | null—
PropTypeDefaultDescription
loadingboolean—Shows a spinner over the label, blocks activation and sets aria-busy. The label keeps its box at opacity: 0, so the button's width does not move and its accessible name survives (API-5, A11Y-12).
size"icon-sm" | "icon-xs" | "sm" | "xs" | null—
type"button" | "reset" | "submit"—
variant"default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null—

InputGroup, InputGroupInput, InputGroupTextarea and InputGroupText add no props of their own — each accepts everything the underlying element accepts.

Accessibility

  • The group is a role="group"; the control inside it keeps every native semantic, so labelling is the control's job, exactly as for a bare Input.
  • Clicking an addon focuses the control, unless the click landed on a button inside it.
  • The group carries the focus affordance: while its input or textarea has focus, the group's border darkens subtly (border-ring/50, easing in over 150ms) — text entry's exception to FOC-14 (MK 2026-09-27). No background changes, on the group or on the control inside it (which has no border and no fill), and there is no ring anywhere. An invalid group keeps its destructive border while focused. An addon button is a button: its own focus keeps the global background tint.
  • Under forced-colors: active the group (data-field-group) carries the outline and the control inside stands down, so the two never paint a double ring (FOC-7).
  • Give every icon-only InputGroupButton an aria-label or an sr-only label; the design lint fails a nameless one.
ContractStates tested
Behaviourdefault, disabled, invalid
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test, grouped, invalid, disabled
Visualdefault, focus, invalid, disabled

Do / Don't

Do
Put the addon after the control in the DOM and use align to place it — that is what keeps click-to-focus correct.
Don't
Nest an Input inside an InputGroup — InputGroupInput is the control that wires the group's state.

Deviations

Upstream's file plus packages/ui/upstream/patches/input-group.patch. Every hunk:

  • FOC-1, FOC-3, FOC-6 — the group's has-[[data-slot=input-group-control]:focus-visible]:border-ring plus ring-3 ring-ring/50 is removed, and the group gains data-field-group, which the forced-colours fallback keys on. There is no ring and no glow.
  • FRM-16 — the inline-start and inline-end addons add group-has-[>textarea]/input-group:self-start and pt-2.5, so beside a textarea they sit level with the first line instead of the textarea's middle.
  • FOC-14 — the invalid border is has-[[data-slot][aria-invalid=true]]:border-destructive, held in every state, focused or not; the has-aria-invalid halo rings go with it.
  • FOC-14 text-entry exception (MK 2026-09-27) — the group adds has-[input:focus,textarea:focus]:not-has-aria-invalid:border-ring/50 with transition-[color,background-color,border-color] duration-150 ease-out: a subtle darker border on the GROUP that eases in while its control has focus — [data-slot=command-input] and the popup search rows included — and no fill anywhere (base.css excludes text inputs and textarea from the background tint). The not-has-aria-invalid guard keeps the destructive border; an addon button's focus keeps the tint.
  • FOC-6 — InputGroupInput and InputGroupTextarea drop ring-0 focus-visible:ring-0 aria-invalid:ring-0, which existed only to cancel the ring Input and Textarea no longer have.
  • API-26 — NO HUNK: InputGroupInput renders Input and InputGroupTextarea renders Textarea, both Base UI Field.Controls, so inside a Field they take their label, description and error ids and aria-invalid from it.
  • DOC-2 — cn is imported from @vegastack/design.
  • FOC-16 — text entry shows hover as a darker input-hover border, focus as the ring/50 border alone (no ring or glow; an invalid field keeps only its destructive border), and read-only as a faint readonly fill on a transparent border. Operator, Regent, 2026-10-31; halo removed 2026-11-01.

On this page