Dots

Text Field

A labelled single-line input with a description, an error, and an optional prefix or suffix, wired up for you.

This username is taken.
"use client"

import * as React from "react"

The TextField component is a labelled single-line input built on the React Aria TextField. The label, the description, and the error are linked to the input for you, with no id, htmlFor, aria-invalid, or aria-describedby by hand. Use it for names, handles, emails, references, and promo codes. For money amounts, phone numbers, or one-time codes, use Amount Field, Phone Field, or Input OTP; for a layout the props cannot express, compose Field with Input yourself.

Installation

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

Usage

import { TextField } from "@/components/ui/text-field"
<TextField
  label="Reference"
  description="The recipient sees this."
  value={reference}
  onChange={setReference}
/>

Text Field vs Field

Use TextField for a single-line text input with a label, a description, and an error. It renders the whole field from props and keeps the error and description linked to the input.

Use Field with Input or InputGroup when you need a custom layout, such as a horizontal field, several inputs under one label, or a control other than a text input.

Features

  • Label, description, and error linked to the input automatically
  • The error replaces the description while the field is invalid
  • The error is read through aria-describedby when the field gets focus, never announced as an alert
  • prefix and suffix inside the field, such as @ or a spinner, without remounting the input when they come and go
  • Native or custom validation through React Aria, including isRequired and validate
  • focusOrAnnounce to bring the user to the field after a failed submit

Description

Add a description for help that the user needs before typing. It is hidden while an error is shown.

Only you see this note.

"use client"

import { TextField } from "@/components/ui/text-field"

Prefix

Pass prefix to show text or an icon before the value, such as @ for a username. See the demo at the top of this page.

<TextField
  label="Username"
  prefix="@"
  autoCapitalize="none"
  autoCorrect="off"
  spellCheck="false"
/>

Suffix

Pass suffix to show text, an icon, or an InputGroupButton after the value, such as a spinner while a lookup runs. Passing the prop at all, even as undefined, keeps the input inside an input group, so focus stays in the field while the suffix changes.

Checking…

"use client"

import * as React from "react"

Invalid

Set isInvalid and pass an errorMessage that says what to fix. The error takes the place of the description.

Enter the full address, e.g. maya@example.com.
"use client"

import { TextField } from "@/components/ui/text-field"

Required

Set isRequired to check the field on submit with native validation. Without errorMessage, the browser's validation message is shown.

"use client"

import { Button } from "@/components/ui/button"

Focus after a failed submit

When a server check fails after submit, call focusOrAnnounce once the error is rendered. It moves focus to the field so the error is read through aria-describedby. When the field already has focus, because the form was sent with Enter, it announces the error politely instead.

"use client"

import * as React from "react"
React.useEffect(() => {
  if (attempt && error) focusOrAnnounce(inputRef.current, error)
}, [attempt, error])

Disabled

Set isDisabled for a value the user cannot change here. Use the description to say how to change it.

Matches your ID. To change it, contact support.

"use client"

import { TextField } from "@/components/ui/text-field"

Accessibility

TextField is built on the React Aria TextField. The label is linked to the input, the invalid and required states are exposed from isInvalid and isRequired, and the description or error is linked through aria-describedby. The error has no role="alert", so it is read when the user reaches the field and not in the middle of typing.

Label the field

Pass label for a visible label. When the design has no visible label, pass aria-label instead. A placeholder is not a label.

Error text

Keep the error to what the user should do, such as "Enter the full address". The error icon is decorative, so the meaning is in the text and not only in the color.

Input hints

Set autoCapitalize="none", autoCorrect="off", and spellCheck="false" for handles, codes, and emails, and set type, inputMode, and autoComplete to match the value, so mobile keyboards and autofill help the user.

API Reference

TextField

Also accepts all React Aria TextField props, such as value, defaultValue, onChange, type, name, inputMode, autoComplete, isRequired, isReadOnly, validate, and validationBehavior.

PropTypeDefaultDescription
labelReact.ReactNode-The visible label above the input. Without it, pass aria-label.
descriptionReact.ReactNode-Help under the input. Hidden while an error is shown.
errorMessageReact.ReactNode | ((validation: ValidationResult) => React.ReactNode)-What to fix. Shown while the field is invalid. Without it, the browser's validation message is shown.
prefixReact.ReactNode-Text or an icon before the value. Passing the prop renders the input inside an input group.
suffixReact.ReactNode-Text, an icon, or an InputGroupButton after the value. Passing the prop renders an input group.
placeholderstring-A sample value shown while the input is empty.
autoCapitalizeReact.InputHTMLAttributes<HTMLInputElement>["autoCapitalize"]-Use "none" for handles, codes, and emails.
inputRefReact.Ref<HTMLInputElement>-A ref to the input element, such as for focusOrAnnounce.
isInvalidboolean-Whether the value is invalid.
isDisabledboolean-Whether the field is disabled.
classNamestring-Additional classes to apply to the root element.

focusOrAnnounce

Call it after a failed submit, once the error is rendered.

function focusOrAnnounce(input: HTMLInputElement | null, message: string): void
ParameterTypeDescription
inputHTMLInputElement | nullThe input to focus. Nothing happens when it is null.
messagestringThe error to announce when the input already has focus.