Skip to content

Form validation

Two fields and a checkbox whose Next control is guarded by the page’s own check: a bad email or a short password is refused with an error under the instruction, and an unticked box with the hole alone.

The story
const story = {
id: 'form-validation',
steps: [
{
id: 'email',
target: { elements: 'input[name="email"]', interactive: true },
message: 'Enter the address you want to sign in with.',
validate: (el) => /.+@.+\..+/.test((el as HTMLInputElement).value),
// Under the instruction rather than over it. Pressing Next with an
// empty field used to leave the step asking for nothing.
error: 'That does not look like an email address yet.',
},
{
id: 'password',
target: { elements: 'input[name="password"]', interactive: true },
message: 'Pick a password of at least eight characters.',
validate: (el) => (el as HTMLInputElement).value.length >= 8,
// Worked out from what was typed, which is what the function form is
// for. `types.ts` says when it is asked.
error: (el) =>
`Eight characters at least, and that is ${(el as HTMLInputElement).value.length}.`,
},
// No `error`, so the hole is the only answer this refusal has: it shakes,
// or under prefers-reduced-motion dims where it stands and opens again.
// Watch it by pressing Next with the box unticked, at ×1 and under
// prefers-reduced-motion. DESIGN.md, **A failed attempt**.
{
id: 'terms',
target: { elements: '[data-terms]', interactive: true },
message: 'Agree to the terms before the account is made.',
validate: (el) => el.querySelector<HTMLInputElement>('input')?.checked === true,
},
{
id: 'submit',
target: { elements: 'button[type="submit"]', interactive: true },
message: 'Now create the account.',
},
],
} satisfies LekoStory