Skip to content

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/react

For 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.

Reference

Propsalign, status, variant… (14)

Props

InputGroup

InputGroup adds no props of its own. They pass through to ComponentProps<'div'>.

InputGroupAddon

InputGroupAddon props
PropType
align'inline-end' | 'inline-start' | 'block-start' | 'block-end'

default 'inline-start'

'inline-start'

InputGroupButton

InputGroupButton props
PropType
variantButtonVariant
'default''danger''ghost''outline''link''secondary'

default | secondary | outline | ghost | danger | link.

default 'ghost'

'ghost'
loadingboolean

Swaps 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 false) on any button that can load, so its live region exists before the first announcement. Without it, a click handler that returns a promise puts the button in this state until the promise settles; an explicit loading (even false) wins. For a success or error label after the work, use AsyncButton.

–
loadingLabelstring

Announced when loading starts.

default "Loading"

"Loading"
soundboolean

With sound on (YoujProvider’s sound), the button plays tap as it's pressed. false keeps this one silent.

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

Motion tokens
MomentTokens
Hover and focusthe frame: border color and the focus halo
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Buttonspress and hover, as on Button
duration.fastease.standard

140ms · 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 an aria-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-none text. Anything in them that people need to read, like a unit, belongs in the label or description as well.