Skip to content

ComponentsForms

Text Field

A single-line text input with a label, help text and validation. Supports icon and text adornments, every native input type, native and custom validation, server errors and form submission.

React AriaSource
We'll send the invite here.

Installation

pnpm dlx shadcn@latest add @desyne/text-field

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { TextField } from "@/components/ui/text-field";
tsx
<TextField label="Email" name="email" type="email" isRequired />

TextField renders the label, the input, the description and the error message, and wires them together for assistive technology. Pass value and onChange to control it, or defaultValue to leave it uncontrolled.

onChange receives a string

React Aria calls onChange with the new value, not a DOM event, so you can pass a state setter directly: onChange={setName}. Use onInput or onKeyDown if you need the raw event.

When to use

  • Text Field — short, free-form, single-line text: names, emails, URLs, codes.
  • Textarea — multi-line text such as comments, bios or descriptions.
  • Number Field — numbers that benefit from steppers, locale formatting and min/max constraints.
  • Search Field — search queries, with a clear button and Esc to clear.
  • Combo Box — free text with suggestions from a list.
  • Input OTP — one-time codes split into individual character slots.

Anatomy

tsx
<TextField>          {/* root: <div>, owns value, validation and state attributes */}
  <Label />          {/* from `label` */}
  <FieldGroup>       {/* the visible box: border, focus ring, invalid state */}
    <FieldAddon />   {/* from `prefix` */}
    <FieldInput />   {/* the native <input> */}
    <FieldAddon />   {/* from `suffix` */}
  </FieldGroup>
  <Description />    {/* from `description` */}
  <FieldError />     {/* shown only while invalid */}
</TextField>
PartRendersNotes
TextField<div>Root. Carries data-slot="text-field", the group/field class and all state attributes.
Label<label>Rendered when label is set. Adds a red * when isRequired.
FieldGroup<div role="presentation">Draws the chrome (fieldVariants). Picks up data-focus-within, data-hovered, data-invalid and data-disabled.
FieldAddon<span>Wraps prefix and suffix. Muted, non-selectable, and sizes any SVG icon inside it.
FieldInput<input>Receives type, name, placeholder, autoComplete, inputMode, maxLength, pattern and every other input prop.
Description<span slot="description">Rendered when description is set. Linked with aria-describedby.
FieldError<span slot="errorMessage">Rendered only while the field is invalid. Shows errorMessage or the validation messages.

Examples

Variants

outline for most forms, filled for dense settings pages and tinted surfaces, underlined for minimal or inline editing. The same variants apply to every field component.

Sizes

sm (28px), md (32px, default) and lg (40px) line up with the button sizes of the same name, so a field and a button sit flush in a row.

Prefix and suffix

prefix and suffix accept icons, text or any element. They sit inside the field, so the whole box highlights on focus. Use text adornments for fixed parts of the value (a scheme, a domain, a currency) that the user shouldn't type.

Password with visibility toggle

A React Aria Button in the suffix toggles type between password and text. Give it an aria-label that describes the action, and set autoComplete so password managers recognise the field.

Input types and hints

type, inputMode and autoComplete pick the right mobile keyboard and let browsers autofill. Turn off spellCheck and autoCorrect for identifiers like repository names or usernames.

Disabled and read only

isDisabled removes the field from the tab order and dims it. isReadOnly keeps it focusable and selectable, so users can still copy the value. Prefer read only for values users need but can't change.

Only owners can rename the workspace.
Read only: can be focused, selected and copied.

Controlled

value and onChange let you derive UI from the value as it's typed, here a URL slug.

URL: acme.dev/p/q3-product-launch

Required and native validation

isRequired, type="email", pattern, minLength and maxLength use the browser's constraint validation. By default (validationBehavior="native") the form won't submit while a field is invalid, and errors appear after the user submits.

Four capital letters, a dash, then four digits.

Custom validation

validate receives the current value and returns an error string, an array of strings, or null / true when valid. With validationBehavior="aria" on the Form (or the field), errors show live as the user types and don't block submission.

Lowercase letters, numbers and dashes.@admin is already taken.

Server errors

Pass validationErrors to React Aria's Form, keyed by field name, to show errors returned by your API. Each error clears as soon as the user edits that field. For a single field, isInvalid with errorMessage does the same.

Form submission

Give the field a name and its value is included in FormData, exactly like a native <input>. type="reset" restores each field's defaultValue.

Custom composition

When the props aren't enough, build the field from the parts in field.tsx inside React Aria's TextField. Here a label row with a plan hint and an inline action button. Add group/field to the root so the label reacts to the disabled and required states.

Pro plan
Point a CNAME record at cname.acme.dev first.

Recipes

Copy to clipboard

A read-only field with a ghost icon button in the suffix. The value stays selectable, and the button confirms the copy.

Anyone with this link can join as a member.

Inline subscribe form

A label-less field (aria-label) next to its submit button, with a pending state and a confirmation.

Product updates

One email a month. No spam, unsubscribe anytime.

Profile settings

A card form with a two-column grid, adornments, autoComplete hints and a footer with reset and submit.

Profile

This is how others will see you on the site.

Your profile lives at acme.dev/@priya.

Accessibility

  • The label, description and error message are linked to the input with aria-labelledby and aria-describedby. Clicking the label focuses the input.
  • Always provide a label: label, aria-label, or aria-labelledby. A placeholder is not a label.
  • Invalid fields get aria-invalid, and the error text is announced as part of the description.
  • The required asterisk is decorative (a CSS pseudo-element). Screen readers get aria-required (or native required with native validation) instead.
  • Icon-only buttons in prefix or suffix need an aria-label. Icons that are purely decorative need nothing: lucide icons are aria-hidden by default.
  • isReadOnly fields stay in the tab order; isDisabled fields don't.

Keyboard

KeyAction
TabMoves focus to the input, then to any focusable adornment
EnterSubmits the surrounding form

Styling

Data attributes

On the TextField root. Children can react with group-data-*/field: variants, e.g. group-data-invalid/field:text-destructive.

AttributePresent when
data-disabledisDisabled is true
data-readonlyisReadOnly is true
data-requiredisRequired is true
data-invalidValidation failed, or isInvalid is true

On the FieldGroup (the visible box):

AttributePresent when
data-hoveredHovered with a mouse or pen
data-focus-withinThe input (or an adornment) has focus
data-focus-visibleFocus inside came from the keyboard
data-invalid / data-disabledMirrors the field state

The input itself exposes data-focused, data-focus-visible, data-hovered, data-invalid and data-disabled.

Slots

data-slotElement
text-fieldRoot
label, description, field-errorField text
field-groupVisible box
field-input<input>
field-addonPrefix and suffix wrappers

Render props

className on TextField styles the root and also accepts a function of the field state:

tsx
<TextField
  label="Email"
  className={({ isInvalid }) => (isInvalid ? "animate-shake" : "")}
/>

Style helpers

field.tsx exports the building blocks and their styles, shared by every field component:

tsx
import {
  bareInputStyles, // classes for an <input> inside a FieldGroup
  fieldVariants,   // tailwind-variants for the box: { variant, size }
  inputVariants,   // same look for a standalone <input>, keyed off data-focused
} from "@/components/ui/field";

API Reference

TextField

Prop

Type

Also accepts every prop of React Aria's TextField, including clipboard, composition and selection event handlers.

Building blocks

Exported from @/components/ui/field and shared by every field component.

ExportWrapsProps
LabelReact Aria LabelLabelProps
DescriptionReact Aria Text with slot="description"TextProps
FieldErrorReact Aria FieldErrorFieldErrorProps; children may be a function of the validation result
FieldGroupReact Aria GroupGroupProps plus variant and size
FieldInputReact Aria InputInputProps; unstyled, for use inside FieldGroup
InputReact Aria InputInputProps plus variant and size; a standalone styled input without adornments
FieldAddon<span>ComponentProps<"span">
  • Textarea — the multi-line version.
  • Number Field — numeric input with steppers and formatting.
  • Search Field — search input with a clear button.
  • Combo Box — text input with a list of suggestions.
  • Select — shares the same field variants and sizes.