TextField
A labelled text input or textarea, with an optional hint and error. A single-line field can also have a start icon and clear button.
On this page
Only used to reply to you.
TextFieldExample.tsx
import { TextField } from "@/components/TextField";
export function TextFieldExample() {
return (
<TextField
label="Email"
type="email"
hint="Only used to reply to you."
autoComplete="email"
required
/>
);
}Examples
Hint
Letters and numbers only.
Error
Enter an email address, like name@example.com.
Required
Optional
Multiline
A few lines is plenty.
Sizes
Search
Clearable
Start icon
Hidden label
Disabled
Props
Unlike the other components, TextField's native props (value, defaultValue, onChange, name, required, disabled, placeholder, autoComplete, ref and the rest) go to the <input>, or the <textarea> in a multiline field, since they belong to it. className and style go to the wrapper, so layout and token overrides cover the whole field. Clearing sets the value and fires an ordinary change, so onChange sees it as it sees typing.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The visible label and the field's accessible name. Required by the type. Plain text, so the clear button's name can be built from it. |
hideLabel | boolean | false | Hides the label visually; it still names the field. Only where something else on screen says what the field is for. It can't be combined with required or optional, since their visible cues are in the label. |
optional | boolean | false | Adds a visible "(optional)" to the label, for a form that marks its optional fields. It can't be combined with required or hideLabel. |
hint | ReactNode | — | Help text under the label, read as the field's description. |
error | ReactNode | — | An error message under the field, with a warning icon. Marks the field invalid and is read after the hint. |
type | "text" | "search" | "email" | "url" | "tel" | "number" | "text" | The native input type. "search" adds a search icon that hides while the field has focus, and the clear button. "number" takes the native min, max and step. |
size | "sm" | "md" | "md" | 32px or 40px tall, matching Button. In a multiline field, this sets where the first line of text sits. |
multiline | boolean | false | Renders a <textarea> that resizes vertically. Native textarea props apply, and type, iconStart, clearable and clearLabel don't. |
rows | number | 6 | A multiline field's visible lines. Only with multiline. |
iconStart | IconName | — | A decorative icon at the start of the field. A search field shows "search" unless this names another. |
clearable | boolean | type === "search" | Shows an icon-only clear button while the field has a value, and lets Escape clear it. |
clearLabel | string | `Clear ${label}` | The clear button's accessible name and tooltip. |
Component tokens
| Token | Copy | Description | Aliases | Light | Dark | Preview |
|---|---|---|---|---|---|---|
--component-text-field-background | The input's background. | color.surface.raised | #fffffffrom color.gray.0 | #18202efrom color.slate.900 | ||
--component-text-field-text | The typed value. | color.text.default | #17202bfrom color.gray.900 | #fffffffrom color.gray.0 | ||
--component-text-field-placeholder | Placeholder text. Never the label. | color.text.subtle | #5d6876from color.gray.600 | #d3dce6from color.slate.200 | ||
--component-text-field-border | The input's boundary (3:1 against the surfaces it sits on and its own background). | color.border.strong | #6d7e90from color.gray.500 | #818b9efrom color.slate.400 | ||
--component-text-field-border-invalid | The boundary while the field has an error. | color.error.border | #c8372cfrom color.red.600 | #e8776cfrom color.red.400 | ||
--component-text-field-border-width | Width of the input's boundary. | effect.border.width | 2pxfrom effect.line.2 | 2pxfrom effect.line.2 | ||
--component-text-field-radius | Corner radius of the input, as on buttons. | radius.control | 4pxfrom radius.4 | 4pxfrom radius.4 | ||
--component-text-field-height-sm | Input height at size sm, matching Button. | size.control.sm | 2remfrom size.8 | 2remfrom size.8 | ||
--component-text-field-height-md | Input height at size md, matching Button. | size.control.md | 2.5remfrom size.10 | 2.5remfrom size.10 | ||
--component-text-field-padding-inline-sm | Inline padding of the input at size sm. | space.inline.sm | 0.5remfrom space.2 | 0.5remfrom space.2 | ||
--component-text-field-padding-inline-md | Inline padding of the input at size md. | space.inline.md | 1remfrom space.4 | 1remfrom space.4 | ||
--component-text-field-font-size-sm | Text size of the input at size sm. | type.size.small | 0.875remfrom type.font-size.sm | 0.875remfrom type.font-size.sm | ||
--component-text-field-font-size-md | Text size of the input at size md. | type.size.body | 1remfrom type.font-size.md | 1remfrom type.font-size.md | ||
--component-text-field-gap | Space between the label, hint, input and error. | space.stack.xs | 0.25remfrom space.1 | 0.25remfrom space.1 | ||
--component-text-field-label-weight | Weight of the label. | type.weight.strong | 600from type.font-weight.600 | 600from type.font-weight.600 | ||
--component-text-field-hint-text | Hint text under the label. | color.text.muted | #455464from color.gray.700 | #e6ecf3from color.slate.100 | ||
--component-text-field-error-text | Error message under the input, beside its warning icon. | color.error.text | #b3261efrom color.red.700 | #f4a097from color.red.300 | ||
--component-text-field-icon | The start icon (decorative; the label names the field). | color.text.muted | #455464from color.gray.700 | #e6ecf3from color.slate.100 | ||
--component-text-field-icon-size | Size of the start icon. | size.icon.sm | 1remfrom size.4 | 1remfrom size.4 | ||
--component-text-field-disabled-background | The input's background while disabled. | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-text-field-disabled-text | The value's color while disabled. | color.text.subtle | #5d6876from color.gray.600 | #d3dce6from color.slate.200 |
Accessibility
- A native <input> with a visible <label> tied to it, so clicking the label focuses the field and the label is its name. A label is required by the type.
- The hint and the error are read as the field's description, hint first. An error also sets aria-invalid, and shows a warning icon beside its text, so it isn't shown by color alone.
- Required fields use native required, which screen readers announce. The visible "(required)" is hidden from them, so it isn't read twice. It's smaller and muted, so it doesn't read as part of the label.
- Optional fields say so in the label. Nothing else announces "(optional)", so unlike "(required)" it stays in the accessible name.
- A multiline field is a native <textarea> with the same label, hint and error behavior, and Enter adds a line. It has no clear button, so a stray Escape can't wipe a long message, and it can be resized vertically.
- Disabled fields use native disabled: they can't be focused and aren't submitted with a form.
- The start icon is decorative. A search field's icon is hidden while the field has focus, and the text moves into its space, so there's no gap before what's typed.
- The clear button is icon-only, named "Clear" and the label, with its name as a tooltip. It follows the field in Tab order and puts focus back in the field. The browser's own search clear button is hidden, so there's only one.
- The boundary is checked to 3:1 against the surfaces, text and the placeholder to 4.5:1, in both themes. In forced colors the boundary takes a system color and the focus ring stays.
| Key | Action |
|---|---|
| Tab | Moves into the field, then to its clear button when it shows one. |
| Escape | Clears a clearable field that has a value, keeping focus in it. With no value, it does nothing, so a surrounding dialog can still close. |
| Enter | In a multiline field, adds a line. In a single-line field inside a form, submits the form. |