Validation
Validate a form field, and write the message that tells someone how to fix it.
Every Luke UI form field can report a problem. An invalid field takes the danger border colour and renders a message beneath the control.
errorMessage means one thing: this field currently has a controlled error. Pass it when you
already have a message from a form library, your server, or your own state. The field is invalid as
soon as you pass a non-empty message. When the field should generate its own message instead, from a
constraint or a validate rule, leave errorMessage out entirely.
Let the browser validate
Set isRequired on TextField, ComboboxField, or Checkbox to require a value before the form
submits. Pass minLength, maxLength, or pattern on TextField to state a rule for the text.
Pass type="email" or type="url" to check the value's format.
Leave out errorMessage. The field renders the message the constraint produces. It appears on blur
or on submit.
Pass validationBehavior="native" to block submission until every field passes. This is the
default.
Pass validationBehavior="aria" to mark an invalid field for assistive technology. The form still
submits. Set this on every field a form library wraps, so the library stays the only thing deciding
whether the form is valid. Native behaviour calls setCustomValidity on a rejected field, so the
browser blocks the submit event and the library never runs.
Custom validation
Pass a validate function for a rule the browser cannot check, such as a reserved username. The
function takes the value and returns the validation message, or null when the value is valid.
Leave out errorMessage. The field renders the message validate returns.
Return one actionable message, even when several conditions could fail. Check the most important condition first so the next message appears once that one is fixed. You can return an array of messages, but the field does not render them as a list. It joins them with a space, so they read as a single run-on sentence, and even two short messages can fill two lines in a 288px-wide form column.
With the default validation behaviour, the field shows the validation message when someone commits the value, for example on blur or submit, rather than between keystrokes.
Realtime validation
Pass a controlled errorMessage for feedback that must update on every keystroke, such as password
strength. You decide validity here, not the field. Make the field controlled with value and
onChange, and compute errorMessage from that value on every render. An absent message keeps the
field valid, so undefined, null, false, and an empty string all mean no error.
const [password, setPassword] = useState('');
const isTooShort = password.length > 0 && password.length < 12;
<TextField
errorMessage={isTooShort ? 'Passwords need at least 12 characters.' : undefined}
label="Password"
onChange={setPassword}
type="password"
value={password}
/>;This is the exception. Constraint and validate messages normally appear after the value is
committed. This does not distract someone while they type.
Form libraries
Pass the library's current error message as errorMessage. The field goes invalid as soon as the
library reports one, and clears once the library clears it. Read
React Hook Form and TanStack Form for the full
setup, including validationBehavior="aria" so the library stays the only thing deciding whether
the form is valid.
Server validation
Pass server errors through Form from react-aria-components. @luke-ui/react does not export
Form, so import it separately.
import { Form } from 'react-aria-components';
<Form validationErrors={{ username: 'That username is taken.' }}>
<TextField label="Username" name="username" />
</Form>;Pass validationErrors with an object keyed by each field's name. Each field shows its message as
soon as you pass validationErrors. The message clears once someone changes the field's value.
When errors appear and clear
An error appears on blur or on submit. It never appears between keystrokes. An error clears as soon as the value changes.
The controlled errorMessage pattern in the realtime validation section is the exception. It
updates on every keystroke because that is its purpose.
Always pair an invalid field with a message
Colour alone cannot report a problem. Someone who cannot separate the danger border from the resting one gets no signal at all. An invalid field with no message is invisible to them.
TextField and ComboboxField add an error icon inside the control, after the value. The icon says
that something is wrong, not what is wrong, so it does not stand in for the message.
Every invalid field needs a message so the error is not conveyed by colour alone, which is what WCAG
1.4.1 Use of Colour requires. Let a constraint or validate generate the message, or pass your own
through errorMessage. The primitives take isInvalid directly, so pair that state with a message
yourself.
Write the message
An error message has two jobs: name the problem, and name the fix.
errorMessage takes any React content, so a message with a link or emphasis works directly.
| Instead of | Write |
|---|---|
| Invalid input | Enter an email address in the form you@example.com. |
| This field is required | Choose the country where you work. |
| Error | Passwords need at least 12 characters. |
- Say what to do next, not only what went wrong. "Choose a delivery date" beats "No date selected".
- Use the field's own words. If the label says "Work location", the message says work location, not region.
- Keep it to one sentence. A long message pushes the rest of the form down the page.
- Describe what is wrong with the value. Do not blame the reader. "Enter a date after today" rather than "You entered an invalid date".
- Repeat anything someone needs from
description. They may never look back up.