Back

Input Group

An accessible input group built with React Aria Components.

This recipe uses a numeric input as an example, but the InputGroup component works with every combination of buttons and/or inputs inside. It takes care of their border radius change and overlapping borders.

This example's styling is borrowed from Tailwind UI's free components. It also uses React Aria Components' NumberField for improved accessibility.

Best Practices

  • Prefix React Aria Components' imports to avoid confusion with custom components.

    import { Button as AriaButton } from 'react-aria-components';
  • Target specific children's styles with a combination of Tailwind CSS' arbitrary values, the & selector and the new :has variant.

    className="flex has-[+*]:*:rounded-r-none [&+*]:*:-ml-px [&+*]:*:rounded-l-none"

Requirements

Code

import { MinusIcon, PlusIcon } from '@heroicons/react/16/solid';
import {
Button as AriaButton,
type ButtonProps as AriaButtonProps,
Group as AriaGroup,
type GroupProps as AriaGroupProps,
NumberField as AriaNumberField,
Input as AriaInput,
type InputProps as AriaInputProps,
} from 'react-aria-components';
function InputGroupExample() {
return (
<AriaNumberField aria-label="The answer" defaultValue={42} minValue={0}>
<InputGroup>
<Button slot="decrement">
<MinusIcon className="size-4" />
</Button>
<Input />
<Button slot="increment">
<PlusIcon className="size-4" />
</Button>
</InputGroup>
</AriaNumberField>
)
}
function InputGroup({
children,
...props
}: Omit<AriaGroupProps, 'className'>) {
return (
<AriaGroup
className="flex has-[+*]:*:rounded-r-none [&+*]:*:-ml-px [&+*]:*:rounded-l-none"
{...props}
>
{children}
</AriaGroup>
);
}
function Button({
children,
...props
}: Omit<AriaButtonProps, 'className'>) {
return (
<AriaButton
className="rounded-md bg-white px-2.5 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 transition-colors hover:bg-gray-50 disabled:opacity-50"
{...props}
>
{children}
</AriaButton>
);
}
function Input({ ...props }: Omit<AriaInputProps, 'className'>) {
return (
<AriaInput
className="block w-full rounded-md border-0 py-1.5 text-sm leading-6 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600"
{...props}
/>
);
}