Text Input
Single-line text input component with custom styles, sizing, read-only and plain-text display, and native browser input types like email, date, and password.
Overview
TextInput is a styled, accessible replacement for a native single-line <input>. It's built on React Aria's useTextField hook, which wires aria-describedby / aria-labelledby to the ids generated for an associated label, help text, and validation feedback.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return <TextInput placeholder="Example text input" aria-label="Example text input" />
}As a form field
Pass label / help / invalid / invalidFeedback / valid / validFeedback directly to TextInput to render it together with an associated FormLabel, FormHelp, and FormFeedback, already wired up — see Form Field instead when a control needs to be grouped with companion content under one label.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return <TextInput label="Name" help="Enter your full name" />
}Types
TextInput accepts any single-line, text-valued native type: text, password, email, tel, url, search, date, datetime-local, month, week, and time. The value is passed straight through to the underlying <input>, so browser-native behaviors like password reveal toggles, email validation, and date pickers all work as expected.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<TextInput type="email" placeholder="name@example.com" aria-label="email example" />
<TextInput
autoComplete="current-password"
type="password"
placeholder="Password"
aria-label="password example"
/>
<TextInput type="date" aria-label="date example" />
</>
)
}Color and file inputs need bespoke rendering (a swatch, a themed file-selector button) that a plain text field can't provide, so they have their own dedicated components instead — see File Input and Color Input. For a richer date-picking experience with a calendar popup, see Date Picker.
Sizing
Set the size prop to "lg" or "sm" to change the control's height.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<TextInput size="lg" placeholder="Large input" aria-label="Large input example" />
<TextInput placeholder="Default input" aria-label="Default input example" />
<TextInput size="sm" placeholder="Small input" aria-label="Small input example" />
</>
)
}Disabled
Add the disabled boolean attribute to give the control a muted color. Browsers also remove disabled controls from the tab order and from form submission automatically.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<TextInput
placeholder="Disabled input placeholder"
aria-label="Disabled input placeholder example"
disabled
/>
<TextInput
defaultValue="Disabled input value"
aria-label="Disabled input value example"
disabled
/>
</>
)
}Readonly
Add the readOnly boolean attribute to prevent the value from being edited. Unlike disabled, a read-only control stays focusable and is still included in form submission — only typing into it is blocked — and it keeps the same appearance as an editable control.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<TextInput defaultValue="Readonly input" aria-label="readonly input example" readOnly />
<TextInput
defaultValue="Readonly disabled input"
aria-label="readonly disabled input example"
readOnly
disabled
/>
</>
)
}Plain text
Pair the plainText prop with readOnly to render the value as plain text instead of an editable-looking field — useful for read-only values that should still line up with the editable fields around them.
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
return <TextInput label="Email" defaultValue="email@example.com" readOnly plainText />
}Controlled value
Pass value and onChange to control the value from external state. onChange receives the new string value directly rather than a change event, so it can be passed straight to a state setter. Omit them (optionally passing defaultValue instead) to let the component manage its own value.
import { useState } from 'react'
import { TextInput } from '@chassis-ui/react'
export const Example = () => {
const [value, setValue] = useState('john@example.com')
return (
<TextInput
type="email"
value={value}
onChange={setValue}
aria-label="email example"
help={`Current value: ${value}`}
/>
)
}Adorns
Pass adornStart and/or adornEnd to render content — an icon, text, or an actionable button —
at the leading or trailing edge of the input. Either prop switches the rendered markup from a bare
<input> to chassis-css's input help pattern:
a .form-input wrapper containing the adorn(s) around a .ghost-input. Wrap adorn content in
InputAdorn so it picks up the .input-adorn class the wrapper's flex layout targets.
import { Icon, InputAdorn, TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<TextInput
aria-label="Search"
placeholder="Search..."
adornStart={
<InputAdorn>
<Icon name="search-outline" size={16} />
</InputAdorn>
}
/>
<TextInput
aria-label="Amount in dollars"
placeholder="0.00"
adornStart={<InputAdorn>$</InputAdorn>}
adornEnd={<InputAdorn>USD</InputAdorn>}
/>
</>
)
}Set component="button" (or component="a") on InputAdorn for an actionable adorn — a
password reveal toggle, a clear button, or a "forgot password" link.
import { useState } from 'react'
import { Icon, InputAdorn, TextInput } from '@chassis-ui/react'
export const Example = () => {
const [visible, setVisible] = useState(false)
const toggleButton = (
<InputAdorn
component="button"
type="button"
aria-label={visible ? 'Hide password' : 'Show password'}
onClick={() => setVisible(!visible)}
>
<Icon name={visible ? 'eye-slash-outline' : 'eye-outline'} size={16} />
</InputAdorn>
)
return (
<TextInput
type={visible ? 'text' : 'password'}
autoComplete="current-password"
aria-label="Password"
placeholder="Password"
adornEnd={toggleButton}
/>
)
}Accessibility
A decorative icon adorn (rendered via Icon without a title) is hidden from assistive
technology automatically. A text adorn like $ or USD is not announced by screen readers — when
it's essential to understanding the field, include it in the input's own accessible name via
aria-label rather than relying on the adorn alone. An icon-only actionable adorn (InputAdorn
with component="button") needs its own aria-label describing the action, since it has no text
content for assistive technology to announce.
API
TextInput
| Prop | Type | Default |
|---|---|---|
adornEnd? | ReactNode | — |
Content rendered at the input's trailing edge, e.g. a InputAdorn icon, text, or button.
Setting either adornStart or adornEnd renders a .form-input wrapper around a
.ghost-input, matching chassis-css's [input help](https://chassis-ui.com/css/docs/forms/input-adorn) pattern. | ||
adornStart? | ReactNode | — |
Content rendered at the input's leading edge, e.g. a InputAdorn icon, text, or button.
Setting either adornStart or adornEnd renders a .form-input wrapper around a
.ghost-input, matching chassis-css's [input help](https://chassis-ui.com/css/docs/forms/input-adorn) pattern. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
defaultValue? | string | — |
| The value of the input, uncontrolled. | ||
disabled? | boolean | — |
| Toggle the disabled state for the component. | ||
help? | ReactNode | — |
| A description for the field, rendered below the input. | ||
invalid? | boolean | — |
Set component validation state to invalid. Left unset, the component shows as invalid by itself
from a submit it held back (an empty required one, say) until it no longer would, or its
form is reset. Set it, to false too, and it alone decides. | ||
invalidFeedback? | ReactNode | — |
An error message for the field, rendered below the input when invalid is set, and after a
submit the field held back, until it no longer would. | ||
label? | ReactNode | — |
The field's caption, rendered as a FormLabel associated with this input. | ||
onChange? | ((value: string) => void) | — |
| Handler that is called when the value changes. | ||
plainText? | boolean | — |
Render the component styled as plain text. Removes the default form field styling and preserve the correct margin and padding. Recommend to use only along side readonly [docs] | ||
readOnly? | boolean | — |
| Toggle the readonly state for the component. | ||
size? | "sm" | "lg" | — |
| Size the component sm or lg. | ||
type? | "date" | "datetime-local" | "email" | "month" | "password" | "search" | "tel" | "text" | "time" | "url" | "week" | (string & {}) | text |
Specifies the type of component. For color or file inputs, use the dedicated ColorInput or FileInput components instead. | ||
valid? | boolean | — |
| Set component validation state to valid. | ||
validFeedback? | ReactNode | — |
A success message for the field, rendered below the input when valid is set. | ||
value? | string | — |
| The value of the input, controlled. | ||
InputAdorn
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the component. | ||
component? | ElementType | — |
Component used for the root node. Either a string to use a HTML element or a component. Use
"button" or "a" for an actionable adorn, e.g. a password reveal toggle or a clear button.
A "button" gets type="button" unless type is given, so it doesn't submit a form. | ||
ref? | any | — |