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} /> );}