2.0 alpha
These docs describe 2.0. Install it with @once-ui-system/core@alpha.
On 1.8.x? Read the 1.x docs
TrademarkTrademarkDocs
Ctrl k
Search docs...
Form Controls

Input

Updated: September 13, 2026
View on GitHub
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 five sizes: extra small, small, medium, large, and extra large. 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: | | clamp | | --- | --- | | font-size | max(token, 16px) | | line-height | max(token, 1.3 × font-size) | | min-height (xs, s) | 40px and 48px | 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
Prop
Type
Default
idrequired
Ties the label to the field. Required, and must be unique on the page.
string
label
Floats above the value once the field is focused or filled.
string
placeholder
Shown while the field is empty. Not a substitute for a label.
string
size
Field height and type scale.
xllmsxs
m
error
Put the field in its error state.
booleanfalse
errorMessage
Message shown under the field when error is set.
ReactNode
description
Helper text under the field, for the rule the label cannot carry.
ReactNode
corners
Round only some corners — for stacking fields into one block.
nonetoprightbottomlefttop-lefttop-rightbottom-rightbottom-left
className
string
style
React.CSSProperties
prefix
Rendered before the value: an icon, a currency symbol, a select.
ReactNode
suffix
Rendered after the value: a unit, a clear button, a Kbd hint.
ReactNode
variant
"ghost" drops the border and background, for a field inside a surface that already has them.
defaultghost
default
focusRing
booleanfalse
characterCount
Show how much has been typed, against maxLength.
boolean
cursor
Set "interactive" for a read-only field that opens something on click.
undefinedinteractive
validate
Run on a debounce; return a message to show, or null to clear.
(value: ReactNode) => ReactNode | null
loading
Show a spinner in the suffix while something resolves.
booleanfalse
…and every React.InputHTMLAttributes propinherited
Edit this page on GitHub
TrademarkTrademark
Built with curiosity by Lorant One and the Design Engineers Club