Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
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

Add Input Group from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

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

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

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.

API Reference

PropTypeDefaultDescription
align"block-end" | "block-start" | "inline-end" | "inline-start"
PropTypeDefaultDescription
loadingbooleanShows 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"
type"button" | "reset" | "submit"
variant"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"

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: a border tint while the control has focus (FOC-3), and no ring anywhere. The control itself is borderless, so nothing paints twice.
  • 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 becomes has-[[data-slot=input-group-control]:focus]:border-ring/70: a border tint on :focus, for mouse and keyboard alike, and no glow. Batch 4 added the same tint for [data-slot=command-input] — upstream's CommandInput puts cmdk's input inside an InputGroup under its own slot name, so the group never saw it focus.
  • FOC-5 — the invalid tint becomes not-focus-within:has-[[data-slot][aria-invalid=true]]:border-destructive, so a focused group shows its focus cue rather than the error colour; the has-aria-invalid halo rings go with it.
  • FOC-6InputGroupInput 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.
  • DOC-2cn is imported from @vegastack/design.

On this page