The frame round one form control: its label above it, a line of help, and the error message under it when the value is wrong, all tied to the control for the ear by Base UI.
When to use
Use Field to frame one form control built on Base UI with its label above it, a line of help and its error message, where no field component already does.
| Use instead | When |
|---|---|
| TextField | The person types one line of words |
| TextArea | The person types several lines of words |
| NumberField | The person types or steps a number |
| Select | The person picks one value from a short, fixed list |
| SearchField | The person finds a line or a stop |
| Slider | The person picks a number or a range along a track |
| Fieldset | Several controls sit together under one name |
| Checkbox | The control is a checkbox, labelled beside it |
| Switch | The control is a switch, labelled beside it |
Kongens Nytorv
Metro M1, M2 and M3.
The map shows the stops near you
Props
| Name | Type | Default | Description |
|---|---|---|---|
label* | ReactNode | — | Required. The field's name, in sentence case, such as "Stop name": shown above the control, and the control'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 control's name. Set it on the few fields a form doesn't ask for; Field draws no mark for a required one. |
optionalLabel | string | The locale's | Overrides "(optional)", the mark after the label's words with optional. |
description | ReactNode | — | A line of help under the label, 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 value and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the control; left out, the field shows Base UI's own message when the control fails its checks. |
children* | ReactNode | — | Required. The control the field labels: a control built on Base UI, which registers with the field, such as a TextField's box. |
ref | Ref<HTMLDivElement> | — | Reaches the root, the Field.Root <div>. |
slotProps | { label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; } | — | Props for each part: label, description, error. |
label (required)ReactNode, default —- Required. The field's name, in sentence case, such as "Stop name": shown above the control, and the control'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 control's name. Set it on the few fields a form doesn't ask for; Field draws no mark for a required one.
optionalLabelstring, default The locale's- Overrides "(optional)", the mark after the label's words with
optional. descriptionReactNode, default —- A line of help under the label, 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 value and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the control; left out, the field shows Base UI's own message when the control fails its checks.
children (required)ReactNode, default —- Required. The control the field labels: a control built on Base UI, which registers with the field, such as a TextField's box.
refRef<HTMLDivElement>, default —- Reaches the root, the
Field.Root<div>. slotProps{ label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; }, default —- Props for each part:
label,description,error.
Use it
import { Field } 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 FieldRoot, FieldLabel, FieldDescription and FieldError. Field adds no keys: every key is the control's.
| Key | Does | From |
|---|---|---|
Tab, Shift+Tab | Move focus to and from the control, the field's one tab stop; the label, description and error take none | Base UI |
| A press on the label | Focuses the control: natively through htmlFor, or through Base UI's own handler for a hidden label | Base UI |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | 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 |
| The control | The control's own | aria-labelledby the label, and its native label through htmlFor, so "Stop name (optional)" with optional | aria-describedby the description and the mounted error, aria-invalid while invalid and not disabled, disabled |
description | none: a <p> | none | Read as the control's description |
error | none: a <div> | none | Read as the control's description while it is mounted |
The error stays a plain <div>, read through the control's aria-describedby, with no live region of Field's own. Base UI mounts the error only with its message, and a live region inserted with its content is not reliably announced; a role="alert" would interrupt on every keystroke with validationMode="onChange"; and the person hears the error when focus reaches the control, as Base UI's Form moves focus to the first invalid control on submit.
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 control, even hidden, and the
optionalmark is inside it, so the ear hears "(optional)" in the control's name; the description and the error are the control'saria-describedby, so the ear hears both with the control; an invalid field isaria-invalidand its error is words, never only the control's red edge. - Every landmark and named region has an accessible name: none here; Field is not a landmark, and a group of fields is a Fieldset's to name.
- Every default label can be overridden:
optionalLabeloverrides "(optional)", Field's one owned string (see Owned strings). - Every shape the type accepts renders, in every variant:
label,descriptionanderrorrender a string, an element and a list alike; a long Danish compound wraps in each (CR-8); Base UI's several messages render as a list. - 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, and the description while it is not given. - Native semantics first: a native
<label>withhtmlFor, a<p>for the description, and the control's own semantics; Perron adds no ARIA, and Base UI onlyaria-labelledby,aria-describedbyandaria-invalid. - Focus moves in reading order and returns on close: the control is the field's one tab stop, and the label, description and error sit around it in reading order; nothing opens or closes.
- Every target is at least
size.target-minsquare: the control's, which is the control's own; the label is a larger target for the same control, with no size of its own to meet. - Loops stop when the system asks for less motion: none here; Field has no loop and no transition (CR-9).
Owned strings
| Key | English | Override | Seen or heard where |
|---|---|---|---|
optional | "(optional)" | optionalLabel | optional, seen after the label's words with optional, and heard in the control's name, as "Stop name (optional)" |
optional is new, described in en.ts as A field's label: after the words of a field the person may leave empty, as in "Stop name (optional)"., with the Danish, Swedish and Norwegian written by the build. It is lowercase in its brackets, since it follows the label's words rather than starting a sentence, as Content design's sentence case reads.
Every other word is the caller's, through label, description and error, or Base UI's message for a failing check, which is the caller's validate result or the browser's own.
Field marks an optional field, never a required one. optional (default false) adds the owned "(optional)" after the label's words, inside the label element, in color.text.secondary, so it is part of the control's name for the ear as well as the eye; optionalLabel overrides it on one instance. Most fields in a form are asked for, so the few that aren't carry the mark, and the words need no legend, where an asterisk does. Field can't read required, which is the control's prop, so it draws no required marker: TextField, Select and the other dependents take optional and pass it through, and a screen whose label already says it writes nothing more.
Field keeps Base UI's showing of the browser's own message for a failing native check, such as required, when the caller gives no error, as a known exception to the owned strings. The browser writes that message in its own interface language, which may not be PerronProvider's; owning one string per check would take a new key for each of the ten native checks for messages a screen should word for its own field ("Enter a stop name", not "Please fill in this field"); a caller who wants the field's own words gives error, or slotProps.error.match with its own children; and the message appears only after a check fails in the browser, never in the server-rendered markup the contract story's owned-strings check reads.
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 | Use a field component that composes Field, such as TextField or Select, where there is one | Wrap such a component in Field again: it already has its label, and the control would be named twice | review |
| DD-2 | must | Put a control built on Base UI inside, which registers with the field | Put a plain <input> inside: it gets no htmlFor, aria-labelledby or aria-describedby, so it is unnamed for the ear (CR-10) | review |
| DD-3 | must | Give a label that names the value, "Stop name", and hide it with hiddenLabel only where the screen already says it | Leave the field without a label, or let a placeholder stand in for one: it vanishes as the person types | review |
| DD-4 | must | Word an error as what to do, "Enter a stop name" | Blame the person ("You forgot the stop") or say only that it failed ("Invalid input"), against Content design | review |
| DD-5 | must | Give error whenever the field is invalid | Set invalid with no message: the control's red edge alone tells only the eye, and only by its colour | review |
| DD-6 | should | Put help the person needs while typing in description, such as "As written on the stop sign" | Put the help in the error: it appears only after a mistake | review |
| DD-7 | must | Set optional on the few fields the person may leave empty, and leave the rest unmarked | Write "(optional)", "(required)" or an asterisk into label yourself: it misses the locale, reads twice beside Field's mark, and an asterisk needs a legend | review |
| DD-8 | should | Label a checkbox or a switch beside it, by its own label | Use Field for a checkbox or a switch: Field's label sits above the control, where theirs sits beside it | review |