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 Field 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/primitives/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.
Below the sm breakpoint (40rem) a size="sm" input types at 16px so iOS
does not zoom the page on focus; from sm up it stays at 14px (md is
16px at every width).
States
invalid colours the border and focus ring --danger and sets
aria-invalid. unsaved tints the field --input-unsaved; 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 Field
Field generates the control id and wires the Label, FieldDescription
and FieldError to the input. Its invalid becomes the input's default
invalid, so one prop on the field 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' | |
invalid | boolean | Field invalid | aria-invalid, data-invalid and the danger styles. Inherits the enclosing Field when unset |
size | 'sm' | 'md' | 'lg' | 'md' | |
fullWidth | boolean | false | w-full on the frame |
unsaved | boolean | false | Tints the field --input-unsaved and sets data-unsaved; pair with Label unsaved |
leadingContent | ReactNode | — | Inside the frame, before the native input |
trailingContent | ReactNode | — | Inside the frame, after the native input |
disabled | boolean | Field disabled | Leave unset to inherit from Field / 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-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-unsaved | unsaved tint |
--ring, --danger | Focus ring; invalid state |
--radius-md | Corner radius |
--duration-fast | Colour transition |
Accessibility
- A real
<input>; inside aFieldit gets the field'sid,aria-labelledbyandaria-describedby(FieldDescription and FieldError). invalidsetsaria-invalid="true".- Focus shows a 1px ring hugging the border (
focus-within), coloured--dangerwhen invalid. Content inleadingContent/trailingContentis inside the focus ring. disableduses the native attribute, inherited fromField/Fieldsetwhen left unset.- Outside a
Field, passidand useLabel htmlFor, oraria-label.
Related
- Field: the field wrapper that wires label, description and error.
- Label, FieldDescription, FieldError: the parts.
- Textarea: the multi-line twin with the same frame.
- SearchInput and NumberInput: inputs with built-in behaviour.