The text field: a white box with a tinted edge, a brand halo on focus, adornments and a password reveal.
TextInput is the one box every text-like field wears: Textarea, Select's field trigger, DateField, SearchField and PrefixedInput all share its --input-* tokens, so one theme change moves them together.
Focus is drawn on the whole box, adornments included (a leading icon, a trailing unit, the reveal button), never on the bare <input>. Invalid warms the fill and turns the edge to the danger hue; read-only is a dashed sunken box; disabled is flat and sunken.
It is a plain controlled or uncontrolled <input>: every native attribute passes through (type, autoComplete, inputMode, maxLength).
Installation
npx shadcn@latest add KezLahd/rime/fieldAlso installs: base, icon.
Usage
import { Field, TextInput } from "@/components/ui";
<Field label="Email" hint="We send the sign-in link here.">
<TextInput type="email" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</Field>Examples
In a Field
Shown to everyone on the team.
Adornments and reveal
States
Enter a 4-digit postcode.
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
TextInput
| Prop | Type | Default | Description |
|---|---|---|---|
| leadingIcon | ReactNode | ||
| trailing | ReactNode | Right-hand slot inside the box: a unit ("%"), a clear button. | |
| mono | boolean | Monospace with slight tracking, for ID numbers, references, postcodes. | |
| invalid | boolean | Overrides the Field's invalid state. | |
| revealable | boolean | Adds a show/hide toggle. Only meaningful with type="password". | |
| defaultRevealed | boolean | false | Starts with the password readable. For the styleguide and review screens. |
| size | "sm" | "md" | "md" | |
| boxClassName | string | Class for the outer box rather than the <input>. |
Also accepts the native attributes of AriaAttributes, DOMAttributes, HTMLAttributes, InputHTMLAttributes.
Field
| Prop | Type | Default | Description |
|---|---|---|---|
| labelRequired | ReactNode | ||
| hint | ReactNode | ||
| error | ReactNode | Presence marks the control invalid. Say what to do, not just what is wrong. | |
| required | boolean | false | |
| optional | boolean | Shows "(optional)". Use on forms where most fields are required. | |
| id | string | Pass to link a field to an ErrorSummary entry. Generated otherwise. | |
| group | boolean | fieldset + legend, for RadioGroup, a checkbox list, OtpInput, DateField. | |
| labelStyle | "sentence" | "caps" | "sentence" | sentence = 13px bold (default, reads well for long labels). caps = the reference 11.5px uppercase micro-label, for short auth/admin forms. |
| labelAction | ReactNode | Something on the label's right edge, e.g. a "Forgot password?" link. | |
| hideLabel | boolean | ||
| className | string | ||
| childrenRequired | ReactNode |
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --field-focus-border | rgba(var(--rgb-brand), 0.7) / var(--focus-color) | … |
| --field-focus-ring | var(--focus-halo) | … |
| --field-focus-ring-invalid | 0 0 0 4px rgba(var(--rgb-danger), 0.14) | … |
| --field-hover-border | rgba(var(--rgb-brand-deep), 0.3) | … |
| --input-bg | var(--surface-solid) | … |
| --input-border | var(--line-input) | … |
| --input-border-width | var(--border-w-field) | … |
| --input-font-size | var(--text-input) | … |
| --input-height-md | var(--field-h-md) | … |
| --input-height-sm | var(--field-h-sm) | … |
| --input-pad-x | calc(14px * var(--density)) | … |
| --input-radius | var(--r-md) / var(--r-sm) | … |
| --input-shadow | var(--shadow-input) | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --border-w-field | tokens.css | … |
| --danger-base | tokens.css | … |
| --danger-field-fill | tokens.css | … |
| --danger-ink | tokens.css | … |
| --density | tokens.css | … |
| --dur | tokens.css | … |
| --ease | tokens.css | … |
| --field-h-md | tokens.css | … |
| --field-h-sm | tokens.css | … |
| --focus-color | tokens.css | … |
| --focus-halo | tokens.css | … |
| --font-body | tokens.css | … |
| --font-mono | tokens.css | … |
| --ink-body | tokens.css | … |
| --ink-brand | tokens.css | … |
| --ink-disabled | tokens.css | … |
| --ink-muted | tokens.css | … |
| --ink-placeholder | tokens.css | … |
| --ink-secondary | tokens.css | … |
| --line-input | tokens.css | … |
| --r-md | tokens.css | … |
| --r-sm | tokens.css | … |
| --r-xs | tokens.css | … |
| --rgb-brand | tokens.css | … |
| --rgb-brand-deep | tokens.css | … |
| --rgb-danger | tokens.css | … |
| --shadow-input | tokens.css | … |
| --surface-solid | tokens.css | … |
| --surface-sunken | tokens.css | … |
| --text-body | tokens.css | … |
| --text-caption | tokens.css | … |
| --text-input | tokens.css | … |
| --text-small | tokens.css | … |
| --wash-subtle | tokens.css | … |
Accessibility
- Wrap it in <Field label=…>: Field wires the label, hint and error to the control (id, aria-describedby, aria-invalid) through context, so the control needs no ids of its own.
- Placeholder ink holds 4.97:1 on the white box; never put instructions only in a placeholder.
- revealable adds a real button inside the box whose name switches between "Show password" and "Hide password" (no aria-pressed, which would read as "Hide password, pressed").
- Set autoComplete on every personal-data field (WCAG 1.3.5).
Do and don't
Do
- Size the box to the content where the content is short (a postcode), with a className width.
Don't
- Don't style the <input> itself; theme the box with --input-* tokens.
- Don't show an error before the user has left the field or submitted.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open text-input.mdRelated: select, textarea, search-field