Skip to main contentSkip to docs navigation

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.

JSX
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.

Enter your full name
JSX
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.

JSX
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.

JSX
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.

JSX
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.

JSX
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.

JSX
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.

Current value: john@example.com
JSX
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.

$USD
JSX
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.

JSX
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

PropTypeDefault
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

PropTypeDefault
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—