Luke UI
GitHub repository

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.

Validation — Invalid fields

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.

Validation — Constraint validation

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.

Validation — Custom rule

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 ofWrite
Invalid inputEnter an email address in the form you@example.com.
This field is requiredChoose the country where you work.
ErrorPasswords 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.

Continue learning