XDSTextInput@xds/core · TextInput
Preview coming soon
Usage
TextInput collects short-form text like names, emails, or search queries. Use it for single-line values where the expected input is brief. Pair it with validation status to guide users through required or formatted fields.Best practices
| Guidance | Practices |
|---|---|
| Do | Always provide a visible label so users know what the field is for. Only hide the label when surrounding context makes it obvious, like a search bar with a magnifying-glass icon. |
| Do | Use validation status with a message to explain what went wrong — "Email must include @" is better than just turning the border red. |
| Do | Size the input to match the expected content length so users can gauge how much to type — small for zip codes, medium for names, large for URLs. |
| Do | Add a clear button for search and filter inputs so users can quickly reset without selecting all text. |
| Don't | Don't use placeholder text as a replacement for a label — placeholders disappear on focus and are not reliably read by screen readers. |
| Don't | Don't use TextInput for multi-line content like comments or descriptions — use TextArea instead. |
| Don't | Don't mark every field as required — only flag truly mandatory fields so users are not overwhelmed by validation errors. |
Anatomy
| Element | Description | |
|---|---|---|
| Label | required | Text that identifies the field. Always rendered for accessibility even when visually hidden. |
| Description | Helper text between the label and the input that provides additional context or formatting hints. | |
| Start icon | A leading icon inside the input that hints at the expected content, like a magnifying glass for search. | |
| Placeholder | Hint text shown when the input is empty. Disappears on focus. | |
| Clear button | A trailing × button that resets the value and returns focus to the input. | |
| Spinner | Loading indicator that appears during async actions like server-side validation. | |
| Status icon | A trailing icon (error, warning, or success) that communicates validation state. |
Import
tsimport {XDSTextInput} from '@xds/core/TextInput'
Props
| Prop | Type | Description |
|---|---|---|
labelrequired | string | Label text for the input — always rendered for accessibility. |
valuerequired | string | Current value of the input. |
type | 'text' | 'password' | 'email' (default: 'text') | The HTML input type. |
onChange | (value: string, e: ChangeEvent<HTMLInputElement>) => void | Callback fired when the input value changes. |
changeAction | (value: string, e: ChangeEvent<HTMLInputElement>) => void | Promise<void> | Async action fired after onChange (if not prevented). Triggers optimistic update and shows a loading spinner while pending. |
size | 'sm' | 'md' | 'lg' (default: 'md') | Size variant of the input. |
isLabelHidden | boolean (default: false) | Visually hides the label while keeping it accessible to screen readers. |
description | string | Description text displayed between the label and input. |
isOptional | boolean (default: false) | Displays an "Optional" indicator next to the label. Mutually exclusive with isRequired. |
isRequired | boolean (default: false) | Displays a "Required" indicator next to the label and sets aria-required. Mutually exclusive with isOptional. |
isDisabled | boolean (default: false) | Disables the input, preventing interaction and dimming the element. |
isLoading | boolean (default: false) | Puts the input in a loading state, showing a spinner and setting aria-busy. |
placeholder | string | Placeholder text shown when the input is empty. |
labelTooltip | string | Tooltip text displayed in an info icon at the end of the label. |
startIcon | XDSIconType | SVG icon component displayed at the start of the input. See `npx xds docs icons` for valid semantic names. |
status | {type: 'error' | 'warning' | 'success', message?: string} | Validation status — applies a colored border and status icon. If message is provided, displays a floating message below the input. Error type also sets aria-invalid. |
hasClear | boolean (default: false) | Shows a clear (×) button when the input has a value. Clicking it clears the value and returns focus to the input. |
hasAutoFocus | boolean (default: false) | Automatically focuses the input on mount. |
htmlName | string | The HTML name attribute for the input, useful for form submissions. |
Examples
Common configurations, variations, and states.TextInput — IconInputs with a leading icon that hints at the expected content. Use when the icon helps users identify the field faster, like a lock for passwords or an envelope for email.
tsx'use client';import {useState} from 'react';import {XDSTextInput} from '@xds/core/TextInput';import {XDSStack} from '@xds/core/Layout';import {EnvelopeIcon,LockClosedIcon,UserIcon,} from '@heroicons/react/24/outline';export default function TextInputIcon() {const [name, setName] = useState('');const [email, setEmail] = useState('');const [password, setPassword] = useState('');return (<div style={{width: 300}}><XDSStack direction="vertical" gap={3}><XDSTextInputlabel="Full name"value={name}onChange={setName}placeholder="Sarah Chen"startIcon={UserIcon}/><XDSTextInputtype="email"label="Email"value={email}onChange={setEmail}placeholder="sarah@company.com"startIcon={EnvelopeIcon}/><XDSTextInputtype="password"label="Password"value={password}onChange={setPassword}placeholder="Enter your password"startIcon={LockClosedIcon}/></XDSStack></div>);}
TextInput — SearchSearch input with a hidden label, start icon, and clear button. Use for toolbar and header search bars where the icon provides sufficient context.
tsx'use client';import {useState} from 'react';import {XDSTextInput} from '@xds/core/TextInput';import {XDSStack} from '@xds/core/Layout';import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';export default function TextInputSearch() {const [query, setQuery] = useState('');const [filter, setFilter] = useState('design systems');return (<div style={{width: 300}}><XDSStack direction="vertical" gap={3}><XDSTextInputlabel="Search field"value={query}onChange={setQuery}placeholder="Search projects…"startIcon={MagnifyingGlassIcon}hasClear/><XDSTextInputlabel="Search field with value"value={filter}onChange={setFilter}placeholder="Filter…"startIcon={MagnifyingGlassIcon}hasClear/></XDSStack></div>);}
TextInput — SizesSmall, medium, and large inputs side by side. Use small in dense UIs like table filters, medium for most forms, and large for prominent single-field pages.
tsx'use client';import {useState} from 'react';import {XDSTextInput} from '@xds/core/TextInput';import {XDSStack} from '@xds/core/Layout';export default function TextInputSizes() {const [sm, setSm] = useState('');const [md, setMd] = useState('');const [lg, setLg] = useState('');return (<div style={{width: 300}}><XDSStack direction="vertical" gap={3}><XDSTextInputlabel="Small"value={sm}onChange={setSm}placeholder="Enter a value"size="sm"/><XDSTextInputlabel="Medium"value={md}onChange={setMd}placeholder="Enter a value"size="md"/><XDSTextInputlabel="Large"value={lg}onChange={setLg}placeholder="Enter a value"size="lg"/></XDSStack></div>);}
TextInput — StatesError, warning, and success validation states with status messages. Use to show users what went wrong and how to fix it.
tsx'use client';import {useState} from 'react';import {XDSTextInput} from '@xds/core/TextInput';import {XDSStack} from '@xds/core/Layout';export default function TextInputStates() {const [error, setError] = useState('sarah@');const [warning, setWarning] = useState('sarah_chen');const [success, setSuccess] = useState('https://sarahchen.dev');const [errorOnly, setErrorOnly] = useState('test');return (<div style={{width: 300}}><XDSStack direction="vertical" gap={3}><XDSTextInputlabel="Error message"value={error}onChange={setError}placeholder="Enter a value"status={{type: 'error',message: 'Please enter a valid email address.',}}/><XDSTextInputlabel="Warning message"value={warning}onChange={setWarning}placeholder="Enter a value"status={{type: 'warning',message: 'This username is already taken — try adding a number.',}}/><XDSTextInputlabel="Success message"value={success}onChange={setSuccess}placeholder="Enter a value"status={{type: 'success', message: 'URL is valid and reachable.'}}/><XDSTextInputlabel="Status without message"value={errorOnly}onChange={setErrorOnly}placeholder="Enter a value"status={{type: 'error'}}/><XDSTextInputlabel="Disabled field"value=""onChange={() => {}}placeholder="Enter a value"isDisabled/><XDSTextInputlabel="Loading field"value="sarahc"onChange={() => {}}isLoading/></XDSStack></div>);}
TextInput — TypesText, password, and email types plus field-level features: tooltip, required, optional, description, disabled, and loading.
tsx'use client';import {useState} from 'react';import {XDSTextInput} from '@xds/core/TextInput';import {XDSStack} from '@xds/core/Layout';export default function TextInputTypes() {const [password, setPassword] = useState('hunter42');const [email, setEmail] = useState('sarah@example.com');const [tooltip, setTooltip] = useState('');const [required, setRequired] = useState('');const [optional, setOptional] = useState('');const [described, setDescribed] = useState('');return (<div style={{width: 300}}><XDSStack direction="vertical" gap={3}><XDSTextInputlabel="Default field"value={described}onChange={setDescribed}placeholder="Enter your email"description="Descriptions can be used to provide additional information about a field."/><XDSTextInputtype="password"label="Password field"value={password}onChange={setPassword}placeholder="Enter a value"/><XDSTextInputtype="email"label="Email field"value={email}onChange={setEmail}placeholder="Enter a value"/><XDSTextInputlabel="Field tooltip"value={tooltip}onChange={setTooltip}placeholder="Enter your API key"labelTooltip="Your unique API key for authentication. Keep this secret!"/><XDSTextInputlabel="Required field"value={required}onChange={setRequired}placeholder="Enter your username"isRequired/><XDSTextInputlabel="Optional field"value={optional}onChange={setOptional}placeholder="Enter your nickname"isOptional/></XDSStack></div>);}
Showcase source
tsx'use client';import {XDSTextInput} from '@xds/core/TextInput';export default function TextInputShowcase() {return (<div style={{width: 300}}><XDSTextInputlabel="Name"value=""onChange={() => {}}placeholder="Enter your name"/></div>);}