Skip to Content

Input

A styled text input built on Base UI’s Input primitive. Supports start/end adornments, a clear button, and error states via data-invalid.

Import

import { Input } from 'chunks-ui'

Basic Usage

<Input placeholder="Enter your name" aria-label="Name" />

Adornments

Use startAdornment and endAdornment to place icons or text at the edges of the input:

<Input startAdornment={<SearchIcon className="size-4" />} placeholder="Search..." aria-label="Search" />
<Input endAdornment={<span className="text-xs text-muted-foreground">USD</span>} placeholder="0.00" aria-label="Amount" />
USD

Clear Button

Add clearable to show a built-in clear button (the ClearButton component). The button shows while the input has text and can be edited: it hides while the input is disabled or read-only, whichever sets it (disabled, readOnly, or a disabled Field.Root). While it is visible it takes the place of endAdornment, so the two don’t show together. On a type="search" input, Chromium browsers and Safari also draw a clear button of their own. With clearable or onClear, the browser’s button never shows, so the built-in one is the only clear button.

Clearing empties the input the way typing does: onChange and onValueChange fire with ''. With a mouse click, focus stays in the input, because the click doesn’t blur it, so a Field.Root with validationMode="onBlur" doesn’t validate in the middle of a clear. With the keyboard (Tab to the button, then Enter or Space), the input blurs, and focus then returns to it.

<Input type="search" defaultValue="Hello" clearable placeholder="Search..." aria-label="Search" />

An uncontrolled input shows the button while it has text, also after a form reset. React Hook Form’s reset() and setValue() write input.value from code, which fires no event: the button follows such a write when the input next renders, for example when the component that holds the form re-renders.

A controlled input is cleared by the handler that sets its value, so it needs one: onChange, onValueChange or onClear. Without one, the button changes nothing, and each such input warns once in the console in development.

export const ClearableSearch = () => { const [value, setValue] = useState('Hello') return ( <Input value={value} onValueChange={setValue} clearable placeholder="Search..." aria-label="Search" /> ) }

onClear

onClear is called when the clear button is used. Passing it also shows the button, without clearable. With clearable too, the value handlers fire first, as above, and onClear only tells you that it happened. With onClear alone, a controlled input fires no onChange or onValueChange, and keeps its value until your onClear sets it to '':

export const InputWithClearButton = () => { const [value, setValue] = useState('') return ( <Input value={value} onChange={e => setValue(e.target.value)} onClear={() => setValue('')} placeholder="Search..." aria-label="Search" /> ) }

An uncontrolled input (defaultValue) is emptied for you, and onClear only tells you that it happened.

Error State

The input styles change when the data-invalid attribute is present (set automatically by Field.Root with invalid prop, or manually). Field.Error shows only when the field fails validation, so add match to show it whenever you set invalid yourself. match={true} shows the error regardless of invalid, so pass match={hasError} (or render Field.Error only while the field is invalid). This example is always invalid, so match can stay:

<Field.Root invalid> <Field.Label>Email</Field.Label> <Input placeholder="user@example.com" /> <Field.Error match>Invalid email address</Field.Error> </Field.Root>
Invalid email address

With Field

Wrap Input in a Field for labels, descriptions, and validation:

<Field.Root> <Field.Label>Username</Field.Label> <Field.Description>Choose a unique username</Field.Description> <Input placeholder="johndoe" /> </Field.Root>

Choose a unique username

Mobile Keyboards

type, inputMode, enterKeyHint and autoComplete pass through to the <input>. Phones read them to choose the on-screen keyboard and the label of its Enter key, and to suggest saved values. A search field gets a search key:

<Input type="search" enterKeyHint="search" placeholder="Search..." aria-label="Search" />

A decimal value, such as an amount, gets a number keypad with a decimal separator:

<Input inputMode="decimal" placeholder="0.00" aria-label="Amount" />

Text Size

The text is 16px below the md breakpoint (768px by default) and at any width where touch is the main pointer (pointer: coarse), such as a phone or a tablet, and 14px otherwise; a laptop with a touch screen keeps 14px. iOS Safari zooms the page when a field with text under 16px takes focus, and a touch device can be wider than md: an iPad, or a larger iPhone (such as the Plus, Pro Max and Air models) held sideways. Select.Trigger, the Select items and the DatePicker trigger have the same size, so a form has one text size. The 16px assumes the default root font size, because text-base is 1rem.

With a mouse, a text-size class on the input, such as text-lg, sets the size from md up. A max-md: class, such as max-md:text-lg, sets it below md, on a touch screen too; a size under 16px brings the zoom back. A sm: size applies from 640px and wins over the 16px until md, on a touch screen too: with sm:text-sm, an iPhone 17 held sideways (750px) has 14px text, and iOS zooms.

From md up, on a touch screen the 16px wins over those classes. To change the size there, use a md:[@media(pointer:coarse)]: class, such as md:[@media(pointer:coarse)]:text-lg; a size under 16px brings the zoom back. A [@media(pointer:coarse)]: class without md:, or a pointer-coarse: class (Tailwind 4.1 or newer), does not change it there. Below md it does apply, so a phone shows it and a tablet does not. For 18px everywhere, pass text-lg max-md:text-lg md:[@media(pointer:coarse)]:text-lg.

On an Input with startAdornment, endAdornment, clearable or onClear, and in every InputCopy, a string className goes on the wrapper, not the input, so a text-size class there does not change the field. Pass a function, className={() => "md:[@media(pointer:coarse)]:text-lg"}, to reach the input.

Props

NameTypeDefault
startAdornmentReactNode

Element rendered before the input text (e.g. a search icon).

endAdornmentReactNode

Element rendered after the input text (e.g. a unit label).

clearableboolean

Shows a built-in clear button while the input has text and can be edited: it hides while the input is disabled or read-only, whichever sets it (disabled, readOnly, or a disabled Field.Root). While it shows, it takes the place of endAdornment.

Clearing empties the input the way typing does: onChange and onValueChange fire with "", so the handler that sets a controlled value clears it, and onClear, if given, is called afterwards. Focus stays in the input with a mouse click; with the keyboard (Tab to the button, Enter or Space) the input blurs and focus then returns to it.

false
onClear() => void

Called, with no arguments, when the clear button is used. Passing it also shows the button, as clearable does.

With onClear alone, a controlled input fires no onChange or onValueChange and keeps its value until onClear sets it. With clearable too, the value handlers fire first and onClear only notifies. The button hides while the input is disabled or read-only. Inside a disabled Field.Root, it is not called.

labelsPartial<InputLabels>

Replaces the built-in English strings, e.g. to translate them. Keys you leave out keep the default from DEFAULT_INPUT_LABELS.

All other props are forwarded to the underlying Base UI Input.

Labels

labels replaces the built-in English text, for example to translate it. Keys you leave out keep their default from DEFAULT_INPUT_LABELS. See Labels.

NameTypeDefault
clearstring

Name of the built-in clear button.

"Clear"
Last updated on