v0.5

Input Group

Compound input shell that composes Input with icons, addons, buttons, and selects inside a single bordered row.

K

Description

InputGroup is a compound primitive. The root InputGroup wraps InputGroupProvider + InputGroupShell; inside the shell you arrange InputGroupLeadingIcon, InputGroupAddon, InputGroupSlot (which holds the actual InputGroupControl plus optional inline icons or InputGroupKbd), InputGroupButton, and InputGroupSelect. Every sub-part reads size, invalid, disabled, and loading from context, so wrapping the group in a Field is enough to cascade state across the whole row.

Reach for InputGroup whenever a single text value needs visible chrome beyond a plain Input: search bars with a ⌘K hint and a submit button, URL fields with a https:// prefix and a copy button, currency amounts with a trailing picker, phone fields with a country flag select, credit-card numbers with brand badges, or password fields with a built-in show/hide toggle.

For plain single-value text capture without affordances, use Input directly; InputGroup adds a shell wrapper you don't need. For OTP or one-character-per-slot input, reach for InputOTP. For numeric increment/decrement controls, use InputStepper. For multi-control rows that are not a single field (e.g. an address row), use a FieldGroup with multiple Field instances instead.

Installation

pnpm dlx @create-ui/cli add input-group

Anatomy

<InputGroup>
  <InputGroupLeadingIcon />
  <InputGroupAddon />
  <InputGroupSlot>
    <InputGroupControl />
    <InputGroupKbd />
  </InputGroupSlot>
  <InputGroupSelect />
  <InputGroupButton />
</InputGroup>

Usage

import {
  InputGroup,
  InputGroupControl,
  InputGroupSlot,
} from "@/components/ui/input-group"
<InputGroup>
  <InputGroupSlot>
    <InputGroupControl placeholder="Search…" />
  </InputGroupSlot>
</InputGroup>

Examples

Sizes

Three sizes (xs, sm, md (default)) match the surrounding type scale and resize the shell radius, padding, and inner controls. When InputGroup sits inside a Field, Field's size is authoritative and cascades through context; set size on the group only for standalone use.

States

Field propagates invalid, disabled, and loading to every sub-part. invalid swaps the border, outline, and addon tones to error; disabled mutes the row; loading paints the info-tinted outline, drops a spinner into the slot, and disables trailing actions.

With Leading Icon

InputGroupLeadingIcon paints a fixed-square bordered cell (bg-weakest) for an icon or short symbol on the left of the row. Use it when you want the icon to read as part of the chrome rather than as content inside the input.

With Addon

InputGroupAddon is a bordered prefix or suffix for short non-editable copy like https:// or units. Combine it with a trailing InputGroupButton (iconOnly) for copy / share affordances.

https://

With Button

InputGroupButton wraps Button and locks its size to the group's size scale. Drop it after the slot for a trailing submit / action, or use iconOnly for a compact icon affordance.

With Select

InputGroupSelect wraps Select in the compact variant and inherits group context. Reach for it when the trailing affordance is a picker: currency, country, permission scope, or domain suffix.

$

With Kbd

Nest InputGroupKbd inside InputGroupSlot, right of the control, to surface a keyboard shortcut hint (⌘K, /, etc.). The Kbd rescales padding and text with the group's size.

K

Password

<InputGroupControl type="password" /> ships an automatic show/hide eye toggle with no extra wiring required. The toggle disables itself when the group is disabled or loading and exposes aria-pressed for assistive tech.

Credit Card

CreditCardInput is a self-contained field that owns its InputGroup, masks the digits (auto-switching between the default 4-4-4-4 grid and Amex's 4-6-5 layout), detects the brand for the trailing badge, and shows a ✓ / ✗ validity marker. It emits the raw (unmasked) digits plus { cardType, isValid, isPotentiallyValid } through onValueChange. Drop it inside a Field and the size / invalid / disabled / loading cascade applies automatically.

Install it from the registry. The command also pulls in input-group and the useCreditCardInput hook for you:

pnpm dlx @create-ui/cli add credit-card-input

Need full control over the chrome? Compose the parts by hand with the underlying useCreditCardInput hook (add use-credit-card-input).

Date

DateInput owns its InputGroup with a leading calendar icon and masks a fixed DD / MM / YYYY entry (configurable to MDY or YMD via order). It emits a parsed ISO string plus a Date object through onValueChange.

Install it from the registry. The command also pulls in input-group and the useDateInput hook for you:

pnpm dlx @create-ui/cli add date-input

Phone Number

PhoneInput owns the country picker (an InputGroupSelect), formats national-format digits for the selected country via libphonenumber-js, and renders the dial code as a static label in front of the input. It emits a submit-ready E.164 string (e.g. "+15551234567", or "" when empty) plus { country, dialCode, nationalNumber } through onValueChange, re-firing when the country changes, so no manual dialCode + digits assembly is needed.

+1

Install it from the registry. The command also pulls in input-group, select, country-flag, and the usePhoneInput hook for you:

pnpm dlx @create-ui/cli add phone-input

Currency Amount

For currency entry, compose an InputGroupLeadingIcon (the symbol) with a trailing InputGroupSelect that owns the ISO code. The select's prefix slot holds the flag glyph and valueChildren overrides the trigger label so the closed state stays compact.

$
pnpm dlx @create-ui/cli add country-flag

Composition

InputGroup is a convenience wrapper over InputGroupProvider + InputGroupShell. Render the two explicitly when you need to branch logic between the context and the chrome, or wrap a non-standard outer container around the shell.

K

Accessibility

InputGroup keeps native input semantics: the inner <input> carries textbox role, focus order, and IME or autofill behavior. Wrap the group in a Field + FieldLabel to give it an accessible name; the label's htmlFor ties to the control's id.

KeyDescription
TabMoves focus through the group: leading affordance, then control, then trailing button or select.
Shift + TabMoves focus backwards through the same order.
EnterSubmits the surrounding <form> when present.
SpaceToggles the password show/hide button when it has focus.

ARIA notes:

  • InputGroupShell renders with role="group" and sets aria-busy while loading is true.
  • aria-invalid is set on InputGroupControl automatically when Field invalid is in effect; pair it with a FieldError so the message is announced.
  • The control inherits its accessible name from FieldLabel, aria-label, or aria-labelledby; never rely on the placeholder alone.
  • The built-in password toggle uses aria-label="Show password" / "Hide password" and toggles aria-pressed automatically.
  • While loading, the shell sets pointer-events-none and trailing buttons disable themselves to prevent submits mid-flight.

Styling

Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):

<InputGroup className="rounded-full">
  <InputGroupSlot>…</InputGroupSlot>
</InputGroup>

Data slots and attributes: the family sets these for CSS targeting:

  • data-slot="input-group" on the shell wrapper, with data-size="<size>", plus the boolean attributes data-invalid, data-disabled, data-loading (each present only when true).
  • data-slot="input-group-slot" on each slot wrapper.
  • data-slot="input-group-control" on the inner <input>.
  • data-slot="input-group-leading-icon" on bordered leading icon cells.
  • data-slot="input-group-addon" on bordered text addons.
  • data-slot="input-group-toolbar" on toolbar rows, with data-position="<start | end | between>".
  • data-slot="input-group-button" on trailing action buttons.
  • data-slot="input-group-select" on the wrapped trailing select trigger.
  • data-slot="input-group-kbd" on keyboard shortcut hints.
  • data-slot="input-group-helper-icon" on inline status icons.
  • data-slot="input-group-card" on credit-card brand cells (with data-loading when true).
  • data-slot="input-group-password-toggle" on the auto-rendered password show/hide button (with aria-pressed).

Target a specific state in CSS:

[data-slot="input-group"][data-invalid] [data-slot="input-group-leading-icon"] {
  /* … */
}
  • Input: single-element primitive without surrounding chrome; reach for it when no affordances are needed.
  • Field: wraps InputGroup for accessible label, description, and error wiring, plus the size / invalid / disabled / loading cascade.
  • InputOTP: one-character-per-slot OTP field; a distinct shape from InputGroup.
  • InputStepper: numeric value with bracketed −/+ buttons; reach for it when the affordance is increment or decrement.
  • Textarea: multi-line plain field with no chrome; reach for it when you don't need the shell.

API Reference

InputGroup

Compound shell entry point. Wraps InputGroupProvider + InputGroupShell and accepts any standard div attribute. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
invalidbooleanfalseMarks the row as invalid. Inherited from Field invalid when omitted.
disabledbooleanfalseDisables every sub-part. Inherited from Field disabled when omitted.
loadingbooleanfalsePaints the loading state and adds a spinner. Inherited from Field loading.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The group sub-parts.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""md"Size scale; controls height, padding, text size, and shell radius. Inherited from Field when omitted.

InputGroupSlot

Flexible inner row that holds the InputGroupControl plus optional inline icons or InputGroupKbd. Reads size / invalid / disabled / loading from context. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Slot contents, typically an InputGroupControl plus optional adornments.

InputGroupControl

The actual input element. Wraps Input and renders a built-in show/hide toggle when type="password". Extends React.ComponentProps<typeof Input> (without size, which is read from context).

Props

PropTypeDefaultDescription
typestring"text"Native <input> type. Common values: email, password, tel, url, search, number, date.
disabledbooleanfalseDisables the input. Inherited from group context when omitted.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().

InputGroupLeadingIcon

Fixed-square bordered slot painted with --color-weakest for an icon or short symbol on the leading edge of the row. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-An icon or short symbol (e.g. $).

InputGroupAddon

Bordered prefix or suffix slot for short non-editable copy like https:// or units. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Static copy or icon + copy.

InputGroupButton

Trailing action button. Wraps Button, locks size to the group's size scale (xs → md, sm → lg, md → xl), and disables itself while the group is invalid / disabled / loading. Extends React.ComponentProps<typeof Button> (without size).

Props

PropTypeDefaultDescription
variant"primary" | "error" | "success" | "neutral-solid" | "neutral-light" | "inverse-solid" | "inverse-light" | "ghost""neutral-solid"Colour intent. Same scale as Button.
appearance"solid" | "outline" | "ghost" | "soft""soft"Fill weight.
shape"square" | "rounded""square"Corner shape.
iconOnlybooleanfalseRenders a square icon-only button (requires aria-label).
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Button label or icon.

InputGroupSelect

Trailing dropdown affordance. Wraps Select in variant="compact" and inherits size / invalid / disabled from context. Extends React.ComponentProps<typeof Select> plus presentation props.

Props

PropTypeDefaultDescription
placeholderReact.ReactNode-Placeholder rendered when no value is selected.
prefixReact.ReactNode-Content rendered before the value inside the trigger (e.g. flag icon).
valueChildrenReact.ReactNode-Custom value rendering passed into SelectValue.
triggerClassNamestring-Extra classes for the underlying SelectTrigger.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-SelectItem entries.

InputGroupKbd

Inline keyboard shortcut badge. Renders a neutral soft Badge sized to match the group. Extends React.ComponentProps<typeof Badge>.

Props

PropTypeDefaultDescription
leadingIconReact.ReactNode<RiCommandFill>Leading icon. Pass null to show no icon (e.g. single-key shortcuts).
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Key label (e.g. K, /).

InputGroupProvider

Broadcasts size, invalid, disabled, and loading down to nested group parts. Most apps inherit these from Field via InputGroup and never render InputGroupProvider directly. (advanced)

Props

PropTypeDefaultDescription
size"xs" | "sm" | "md""md"Size broadcast to nested group parts.
invalidbooleanfalseMarks nested parts as invalid.
disabledbooleanfalseDisables nested parts.
loadingbooleanfalsePaints the info-tinted loading state on nested parts.
childrenReact.ReactNode-Provider subtree.

InputGroupShell

Visual shell painted by InputGroup. Render it directly only when composing InputGroupProvider with a non-standard outer chrome. Extends React.ComponentProps<"div">. (advanced)

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Shell contents.

InputGroupToolbar

Horizontal toolbar row inside the shell with configurable justification. Extends React.ComponentProps<"div">. (advanced)

Props

PropTypeDefaultDescription
position"start" | "end" | "between""start"Flex justification of the toolbar row.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Toolbar contents (buttons, hints, etc.).

InputGroupCard

Fixed-aspect card slot for trailing brand badges, typically a credit-card brand mark. Extends React.ComponentProps<"div">. (advanced)

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The brand mark or icon.

InputGroupHelperIcon

Small status icon shown inline inside an InputGroupSlot (e.g. ✓ / ✗ / ?). Colours track group context: text-body by default, text-disabled while disabled or loading, and text-error-base when invalid, where the icon also swaps to a warning glyph. Extends React.ComponentProps<"span">. (advanced)

Props

PropTypeDefaultDescription
invalidIconReact.ReactNode<RiErrorWarningFill>Icon shown in the invalid state. Pass null to keep children (e.g. a validity ✓ / ✗).
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The status icon.

InputGroupField

Convenience wrapper that composes Field + FieldLabel + FieldDescription + FieldError around an InputGroup. Reach for it when you don't need to customise the surrounding Field. Extends React.ComponentProps<"div">. (advanced)

Props

PropTypeDefaultDescription
labelReact.ReactNode-Rendered into a FieldLabel above the group.
descriptionReact.ReactNode-Rendered into a FieldDescription below the label.
errorReact.ReactNode-Renders a FieldError and sets invalid on the field.
size"xs" | "sm" | "md""md"Size propagated to the underlying Field and group.
disabledbooleanfalseDisables the field and the group.
childrenReact.ReactNode-The group sub-parts.