Input Group
Put icons, text, shortcuts and buttons inside an input or textarea’s frame.
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use an input group when something belongs inside the field's border: a search icon, a https:// prefix, a unit, a copy button, a shortcut hint. If the extra piece is its own action next to the field, like a Send button after an email address, put a Button beside a plain Input instead.
import { InputGroup, InputGroupAddon, InputGroupInput, Kbd, KbdGroup } from '@youj/react'
import { SearchIcon } from '@youj/icons'
export function Search() {
return (
<InputGroup>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupInput aria-label="Search" placeholder="Search projects…" />
<InputGroupAddon align="inline-end">
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</InputGroupAddon>
</InputGroup>
)
}The group is the frame. The InputGroupInput or InputGroupTextarea inside it is bare, and the frame takes its hover, focus halo, invalid and disabled treatment. Clicking an addon's empty space focuses the control, as it would on a native field. Buttons inside keep their own clicks and their own focus ring.
Description
Input groups holding a search icon, a shortcut hint and a copy button for an API key.
Addons on each edge of the control.
An addon sits on an edge set by align: inline-start (the default) or inline-end beside the control, or block-start or block-end above or below it. The edges are logical, so they swap sides under right-to-left text. Put icons straight in, and wrap text in InputGroupText.
A textarea with a toolbar under it and an async send button.
InputGroupTextarea grows with its content like a Textarea. A block-end addon under it becomes a toolbar. InputGroupButton is a Button sized for the frame. For work the button runs itself, put an Async Button in the addon: the send button in the preview spins, lands on a check and announces "Comment posted", while staying square.
Code
input-group-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsalign, status, variant… (14)
Props
InputGroup
InputGroup adds no props of its own. They pass through to ComponentProps<'div'>.
InputGroupAddon
| Prop | Type | |
|---|---|---|
align | 'inline-end' | 'inline-start' | 'block-start' | 'block-end'default 'inline-start' | 'inline-start' |
InputGroupButton
| Prop | Type | |
|---|---|---|
variant | ButtonVariant'default''danger''ghost''outline''link''secondary'
default 'ghost' | 'ghost' |
loading | booleanSwaps the label for a spinner without changing the button's width, sets
aria-busy, and blocks presses while keeping keyboard focus on the button.
Pass it (even as | – |
loadingLabel | stringAnnounced when loading starts. default "Loading" | "Loading" |
sound | booleanWith sound on ( default true | true |
size | 'sm' | 'xs' | 'icon-xs' | 'icon-sm'default 'xs' | 'xs' |
type | 'button' | 'submit' | 'reset'default 'button' | 'button' |
It defaults to the ghost variant, type="button" and the xs size, so it never submits a form by accident and fits inside a 32px frame.
InputGroupInput and InputGroupTextarea
InputGroupInput takes every Input prop, and InputGroupTextarea every Textarea prop. InputGroupText is a styled <span>.
MotionHover and focus, buttons · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Hover and focusthe frame: border color and the focus halo | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Buttonspress and hover, as on Button | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
The frame only changes color. Buttons inside follow Button's reduced motion: no press scale, and async states swap instantly.
Reduced motion
The frame's border and halo change color as usual, since nothing moves. Buttons in the group lose their press scale, and async buttons swap states instantly.
AccessibilityKeyboard, ARIA
Accessibility
- The frame is a
group. The control inside is the labeled element: put the InputGroup inside a Field and the FieldLabel names the control, or give the control anaria-label. - Icon-only buttons inside need an
aria-label, like any icon-only button. - A reveal button, like the password eye above, keeps one name and reports its state with
aria-pressed. - Keyboard hints such as ⌘ K are visual. If the shortcut matters, say it in the control's description too.
- Addons are
select-nonetext. Anything in them that people need to read, like a unit, belongs in the label or description as well.