Input
An input is Headless UI's Input rendered inside a styled frame. The frame
carries the border, background, size and focus ring from the control tokens;
the native <input> inside is transparent. Inside a FormItem it receives the
field's id, aria-labelledby, aria-describedby, aria-invalid and disabled
state without any ids on your side.
import Input from '@zuilib/components/input'
Variants
outline is the default field: --input border on --background. ghost
has no border until it is hovered or focused, for inline editing.
Sizes
sm, md and lg read --control-height-*, --control-padding-x-* and
the matching --text-* step, the same scale every control shares.
States
state colours the border and the focus ring: error uses --destructive
and also marks the control invalid, success uses --success. unsaved
tints the field --input-dirty; disabled dims it and blocks the pointer.
Leading and trailing content
leadingContent and trailingContent sit inside the frame, beside the
native input, so they share its border and focus ring. Icons should be
aria-hidden (the built-in ones already are).
In a FormItem
FormItem generates the control id and wires the Label, Description
and Message to the input. Its invalid becomes the input's default
invalid, so one prop on the item paints the border and sets aria-invalid.
Props
InputProps extends InputHTMLAttributes<HTMLInputElement> (minus the
native size) with:
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'outline' | 'ghost' | 'outline' | |
state | 'default' | 'error' | 'success' | 'default' | Border and ring colour; error also marks the control invalid. Exposed as data-state |
invalid | boolean | FormItem invalid | aria-invalid, data-invalid and the error styles. Inherits the enclosing FormItem when unset |
size | 'sm' | 'md' | 'lg' | 'md' | |
fullWidth | boolean | false | w-full on the frame |
unsaved | boolean | false | Tints the field --input-dirty and sets data-unsaved; pair with Label isDirty |
leadingContent | ReactNode | — | Inside the frame, before the native input |
trailingContent | ReactNode | — | Inside the frame, after the native input |
disabled | boolean | FormItem disabled | Leave unset to inherit from FormItem / Fieldset |
className | string | — | Merged last onto the frame (data-slot="input") |
inputClassName | string | — | Merged last onto the native <input> |
Every other prop (type, value, onChange, placeholder, name,
autoComplete, data-*, …) lands on the native <input>. Mouse and
pointer enter/leave handlers are attached to the native input too, so
currentTarget is the element you expect.
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="input"] | div | The frame. Carries data-state, data-unsaved and Headless UI's data-focus / data-hover / data-disabled / data-invalid |
[data-slot="input-control"] | input | The native input; mirrors the same state attributes |
[data-slot="input-leading"] | div | Wraps leadingContent |
[data-slot="input-trailing"] | div | Wraps trailingContent |
/* Consumer CSS: pill inputs */
[data-slot="input"] {
border-radius: var(--radius-full);
}
Tokens
| Token | Used for |
|---|---|
--control-height-sm/md/lg | Frame height per size |
--control-padding-x-sm/md/lg | Horizontal padding per size |
--input, --background, --foreground, --muted-foreground | Border, surface, text and placeholder |
--input-dirty | unsaved tint |
--ring, --destructive, --success | Focus ring per state |
--radius-md | Corner radius |
--duration-fast | Colour transition |
Accessibility
- A real
<input>; inside aFormItemit gets the field'sid,aria-labelledbyandaria-describedby(Description and Message) from Headless UI. invalid(orstate="error") setsaria-invalid="true".- Focus shows a 1px ring hugging the border (
focus-within), coloured by the validation state. Content inleadingContent/trailingContentis inside the focus ring. disableduses the native attribute, inherited fromFormItem/Fieldsetwhen left unset.- Outside a
FormItem, passidand useLabel htmlFor, oraria-label.
Related
- FormItem: the field wrapper that wires label, description and message.
- Label, Description, Message: the parts.
- Textarea: the multi-line twin with the same frame.
- SearchInput and NumberInput: inputs with built-in behaviour.