The box a person types one line of words into, such as a stop's name or an email address, framed by the Field that gives it its label, its line of help and its error message.
When to use
Use TextField for one line of words a person types, such as a stop's name or an email address, with its label, its line of help and its error.
| Use instead | When |
|---|---|
| TextArea | The words run to sentences, over several lines, such as what the person saw on a trip |
| SearchField | The words find a line or a stop, with suggestions under the box |
| NumberField | The value is a number, typed or stepped up and down |
| Select | The value is one of a short, fixed list |
Kongens Nytorv
Metro M1, M2 and M3.
As written on the stop sign
Variants and states
| Rest | Hover | Pressed | Focus | Disabled | |
|---|---|---|---|---|---|
| Default | As written on the stop sign | As written on the stop sign | As written on the stop sign | As written on the stop sign | As written on the stop sign |
- Rest
As written on the stop sign
- Hover
As written on the stop sign
- Pressed
As written on the stop sign
- Focus
As written on the stop sign
- Disabled
As written on the stop sign
Props
| Name | Type | Default | Description |
|---|---|---|---|
label* | ReactNode | — | Required. The field's name, in sentence case, such as "Stop name": shown above the box, and the input's accessible name. |
hiddenLabel | boolean | false | Whether the label is heard but not seen. Use it only where the screen around the field already says what it is for. |
optional | boolean | false | Whether the person may leave the field empty. Adds "(optional)" after the label's words, seen and heard as part of the input's name. It sets no check: required does. |
optionalLabel | string | The locale's | Overrides "(optional)", the mark after the label's words with optional. |
description | ReactNode | — | A line of help between the label and the box, such as "As written on the stop sign", read after the label by a screen reader. Left out, no description is rendered. |
error | ReactNode | — | What is wrong with the words and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the box; left out, the field shows Base UI's own message when the input fails its checks. |
disabled | boolean | false | Whether the field ignores the person: the box takes no focus and no typing, and the label, the help and the words turn to the tertiary ink. |
name | string | — | The name the words are submitted under in a form's FormData. |
type | 'text' | 'email' | 'password' | 'tel' | 'url' | 'text' | What the words are: plain text, an email address, a password (shown as dots), a phone number or a web address. It sets the phone's keyboard and the browser's own check. |
placeholder | string | — | Words shown in the empty box, such as "Such as Nørreport St.". They vanish as the person types, so they never replace the label or the description. |
required | boolean | false | Whether the field must hold words before a form is submitted: an empty box fails the browser's check, and Base UI shows the error. |
value | string | — | The words in the box, when the caller controls them. |
defaultValue | string | — | The words in the box at first, when the input keeps them itself. Left out, the box starts empty. |
onChange | (event: ChangeEvent<HTMLInputElement, Element>) => void | — | React's own change event, called on every change to the words; event.target.value holds them. Native, so a form library's register() or handleChange connects unchanged. |
ref | Ref<HTMLInputElement> | — | Reaches the root, the <input>. |
slotProps | { field?: Omit<FieldRootProps, "children" | "disabled" | "name"> | undefined; label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; } | — | Props for each part: field, label, description, error. |
label (required)ReactNode, default —- Required. The field's name, in sentence case, such as "Stop name": shown above the box, and the input's accessible name.
hiddenLabelboolean, defaultfalse- Whether the label is heard but not seen. Use it only where the screen around the field already says what it is for.
optionalboolean, defaultfalse- Whether the person may leave the field empty. Adds "(optional)" after the label's words, seen and heard as part of the input's name. It sets no check:
requireddoes. optionalLabelstring, default The locale's- Overrides "(optional)", the mark after the label's words with
optional. descriptionReactNode, default —- A line of help between the label and the box, such as "As written on the stop sign", read after the label by a screen reader. Left out, no description is rendered.
errorReactNode, default —- What is wrong with the words and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the box; left out, the field shows Base UI's own message when the input fails its checks.
disabledboolean, defaultfalse- Whether the field ignores the person: the box takes no focus and no typing, and the label, the help and the words turn to the tertiary ink.
namestring, default —- The name the words are submitted under in a form's
FormData. type'text' | 'email' | 'password' | 'tel' | 'url', default'text'- What the words are: plain text, an email address, a password (shown as dots), a phone number or a web address. It sets the phone's keyboard and the browser's own check.
placeholderstring, default —- Words shown in the empty box, such as "Such as Nørreport St.". They vanish as the person types, so they never replace the label or the description.
requiredboolean, defaultfalse- Whether the field must hold words before a form is submitted: an empty box fails the browser's check, and Base UI shows the error.
valuestring, default —- The words in the box, when the caller controls them.
defaultValuestring, default —- The words in the box at first, when the input keeps them itself. Left out, the box starts empty.
onChange(event: ChangeEvent<HTMLInputElement, Element>) => void, default —- React's own change event, called on every change to the words;
event.target.valueholds them. Native, so a form library'sregister()orhandleChangeconnects unchanged. refRef<HTMLInputElement>, default —- Reaches the root, the
<input>. slotProps{ field?: Omit<FieldRootProps, "children" | "disabled" | "name"> | undefined; label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; }, default —- Props for each part:
field,label,description,error.
Use it
import { TextField } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "Field" })Keyboard and ARIA
From the Base UI reference for Field and the installed FieldControl. "From" says who does it. TextField adds no keys.
| Key | Does | From |
|---|---|---|
| Typing | Changes the words and calls onChange(event); marks the field filled and dirty | Base UI |
Enter | Marks the field touched and runs its checks; in a form, the browser submits it | Base UI |
Tab, Shift+Tab | Move focus to and from the input, the field's one tab stop; leaving marks it touched, and with validationMode="onBlur" runs its checks | Base UI |
| A press on the label | Focuses the input: natively through htmlFor, or through Base UI's own handler for a hidden label | Base UI |
| The rest | Move the caret, select, cut, copy and paste, as the browser's own <input> does | Browser |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | textbox, native, for 'text', 'email', 'tel' and 'url'; none for 'password' in HTML's ARIA mapping, so a role query finds none, though Chromium's tree shows it as a protected textbox | aria-labelledby the label, and its native label through htmlFor: "Stop name", or "Stop name (optional)" with optional | aria-describedby the description, then the mounted error; aria-invalid while invalid and not disabled; required; disabled; readonly |
field | none: a <div> | none | none |
label | none: a native <label>, or a hidden span | Its text: the caller's label, then "(optional)" with optional | none |
description | none: a <p> | none | Read as the input's description |
error | none: a <div> | none | Read as the input's description while it is mounted, with no live region of its own (Field's spec) |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the label names the input, even hidden, with the
optionalmark inside it; the description and the error are the input'saria-describedby; an invalid field isaria-invalidand its error is words beside the red edge, never the edge alone; the placeholder is never the name, and a story proves the input is named by the label with a placeholder given. - Every landmark and named region has an accessible name: none here; TextField is not a landmark, and a group of fields is a Fieldset's to name.
- Every default label can be overridden:
optionalLabeloverrides "(optional)", the one owned string, Field's. - Every shape the type accepts renders, in every variant:
label,descriptionanderrorrender a string, an element and a list alike, as Field's do; each of the fivetypevalues renders its box; a long Danish compound wraps in the label, the description and the error, and stays on one scrolling line in the box (CR-7). - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; the error is unmounted while the field is valid. - Native semantics first: a native
<input>with its owntype,requiredandreadonly, and a native<label>; Perron adds no ARIA, and Base UI onlyaria-labelledby,aria-describedbyandaria-invalid. - Focus moves in reading order and returns on close: the input is the field's one tab stop, between the label and description above it and the error below; nothing opens or closes.
- Every target is at least
size.target-minsquare: the box,size.inputtall and as wide as its container; the label is a larger target for the same input. - Loops stop when the system asks for less motion: none here; TextField has no loop and no transition (CR-8).
Owned strings
| Key | English | Override | Seen or heard where |
|---|---|---|---|
optional | "(optional)" | optionalLabel | Field's optional span, seen after the label's words with optional, and heard in the input's name |
optional is the key packages/perron/src/locale/en.ts already has, Field's. TextField reads no string itself: it passes optional and optionalLabel to Field, which reads it. Every other word is the caller's, through label, description, error and placeholder, or Base UI's message for a failing check: the caller's validate result, or the browser's own message, Field's known exception, which applies here unchanged (Field).
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Give a label that names the words, "Stop name", and hide it with hiddenLabel only where the screen already says it | Let the placeholder stand in for the label: it vanishes as the person types, and it is drawn in color.text.tertiary, below text contrast | review |
| DD-2 | must | Put help the person needs while typing in description, "As written on the stop sign" | Put that help in the placeholder: it is gone once they type | review |
| DD-3 | must | Word an error as what to do, "Enter a stop name" | Blame the person or say only that it failed ("Invalid input"), against Content design | review |
| DD-4 | should | Spread React Hook Form's register('stop') on TextField, or pass Formik's handleChange, as on a native <input> | Wrap TextField in a controller to read its value: its onChange is the native one already | review |
| DD-5 | must | Give error from the form library whenever the field is invalid | Set slotProps.field.invalid with no message: the red edge alone tells only the eye, and only by its colour | review |
| DD-6 | must | Give autoComplete for the person's own details, such as autoComplete="email", so the browser can fill them | Leave a field that asks for a name, an email address or a phone number without autoComplete: WCAG 2.2's identify input purpose (1.3.5) asks for it | review |
| DD-7 | should | Set optional on the few fields the person may leave empty, and required where an empty field must fail | Write "(optional)", "(required)" or an asterisk into label yourself: it misses the locale, and an asterisk needs a legend | review |
| DD-8 | must | Use SearchField to find a line or a stop, and NumberField for a number | Give TextField a type of 'search' or 'number': the type union leaves them out, and SearchField and NumberField carry their own keys and words | types |
| DD-9 | must | Use TextField on its own: it already has its label, its help and its error | Wrap TextField in Field: the input would be labelled twice, as Field's spec says | review |
| DD-10 | should | Set the width on the container, or on the frame through slotProps.field | Give the input a width through style alone: the label and the error take the frame's width, not the box's | review |