The Input component provides a text entry field with floating labels, validation support, and customizable styling. Use it for collecting user input in forms.
The Input component supports error state and message separately. Access them with the error and errorMessage props.
The Input component supports custom validation through the validate prop. This prop accepts a function that receives the input value and returns either an error message or null. The validation is happens automatically on value change with an automatic debounce of 1 second.
Add icons or other elements before or after the input text.
Use the placeholder prop to use placeholder instead of label. The placeholder disappears when the input has value.
Use the description prop to add a description below the input.
Display a character counter with color-coded feedback based on remaining characters. The counter changes color when approaching the limit.
The Input component supports six sizes, from xxs (32px) through xs (40px), s (48px), m (56px, the default) and l (64px) to xl (72px).
Field heights run a step above the button scale: an xs field is 40px, as tall as an m Button. For a toolbar or a filter row, where every control should be the height of a size="s" Button, use size="xxs". It is 32px, rounded at radius-m like the button, with its prefix and suffix 8px in from the edge.
A 32px field has no room for a floating label. At xxs the label shows as the placeholder, unless you pass a placeholder of your own, and stays the field's accessible name through a visually hidden <label>. Every field built on Input (Select, NumberInput, DateInput, DateRangeInput, PasswordInput, ColorInput, TagInput) and Textarea take xxs the same way, and Form size="xxs" passes it to every field inside.
iOS Safari zooms the page when a focused control computes below 16px, and it does not reliably zoom back out — the page is left scrolled somewhere else. Once UI's mobile base is 15px, which put three of the five sizes under that threshold, size="m" among them.
On touch pointers only, the value is therefore clamped:
xxs has no height floor: it is small on purpose, and a floor would make it the same field as xs on a phone. Its 16px value and 20.8px line still fit its 22px box.
Line-height is clamped alongside the font because xs and s resolve --line-height-body-s to 16.88px on mobile — 16px glyphs would otherwise sit in a line box barely taller than themselves.
Nothing changes on a mouse pointer, and l and xl are untouched either way: they already cleared 16px. The visible consequence is that xs, s and m render the same text size on a phone and differ by height alone, which is how iOS treats its own text fields.
The floating label and the value are positioned from where their ink falls — cap height, baseline and descender — so the pair stays optically centred at whatever size and height the clamps produce.
The Input component supports two variants: default and ghost.
Set loading to show a spinner in the input suffix while async validation or data fetching is in progress.
Fields draw no focus ring by default — the quiet, borderless look modern native inputs have settled on. Set focusRing to outline the field on focus, using the same ring Card and SmartLink use, so a keyboard user can see where they are. Worth turning on for any form someone has to tab through.
Note it shows on a mouse click too: :focus-visible always matches a field that takes keyboard input, however that field was focused.
Four components build on Input. They keep everything on this page and add one thing each, so they live on pages of their own:
- NumberInput — steppers, min/max/step, and a numeric onChange
- OTPInput — one box per digit, with paste and an onComplete
- PasswordInput — a reveal toggle
- TagInput — an array of removable chips instead of a string