---
title: "Form design"
description: "How many questions per page, when validation fires, and how errors are surfaced. The behaviour of a question, as distinct from the code that wires a form up."
url: "https://opsinjs.pensievelabs.org/patterns/forms"
source: "https://opsinjs.pensievelabs.org/patterns/forms.md"
section: "Patterns"
kind: "pattern"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["form design", "question design", "form UX", "how forms should behave"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="pattern" />

There are two entirely different questions hiding behind the word "forms", and
most design systems answer only one of them.

**How a form is wired** is a `Field` bound to React Hook Form, a resolver, a
submit handler, and server errors mapped back onto controls. That is mechanics,
and it lives in [Handbook → Forms](../../handbook/forms.mdx).

**How a question behaves** covers whether it shares a page with five others,
whether the error appears while the reader is still typing, and whether
"optional" is marked or "required" is. That is design, and it is what this group
is for. It is also where the majority of real form failures happen, because the
wiring is usually correct and the behaviour is usually inherited from whatever
the last team did.

## When to use [#when-to-use]

Read this group when you are deciding any of the following:

* How many questions belong on one page, and what to do about the reader's sense
  of progress if the answer is "one".
* When validation should run and what that decision costs a reader using a
  screen reader. The timing can be on keypress, on blur, on submit, or some
  combination.
* Where an error is announced, how the reader gets to it, and what happens to
  everything else they had typed.
* Whether to mark required fields, optional fields, or neither.
* Which input type and which `autocomplete` token a field should carry.
* How to accept a number that has a unit attached to it.

For the specific data a health product actually asks for, such as a date of
birth, a name or a set of medications, go to
[Ask users for…](../ask-users-for/index.mdx), which applies everything here to
one field at a time.

## When not to use [#when-not-to-use]

* **You want the code.** Field composition, validation libraries and server
  error mapping are [Handbook → Forms](../../handbook/forms.mdx).
* **You want the component contract.** The parts, states and accessible naming
  of a single field are [Field](../../components/field.mdx).
* **You want the words.** Label and error wording is
  [Content & language](../../content/index.mdx); sensitive phrasing is
  [Asking sensitive questions](../../content/asking-sensitive-questions.mdx).
* **You are collecting a reading, not answering a question.** Repeated numeric
  entry is optimised differently. See [Daily logging](../daily-logging.mdx).
* **It is a consent decision.** Consent is not a form field, however it is
  rendered. See [Consent and permissions](../consent-and-permissions.mdx).

## How it works [#how-it-works]

Every page in this group answers one decision, and the decisions compound in a
fixed order. Making them out of order is how forms end up with an error summary
that nobody can reach and a required-field convention that contradicts itself.

<FlowDiagram>
  {`flowchart TD
    A["A thing you need to know"] --> B{"Do you need it at all?"}
    B -->|"no"| C["Do not ask. This is the largest single win."]
    B -->|"yes"| D["Question pages: how many per screen"]
    D --> E["Required and optional: what is marked and how"]
    E --> F["Autocomplete and input types: what the field is"]
    F --> G["Units and numeric entry: what shape the answer has"]
    G --> H["Validation timing: when the check runs"]
    H --> I["Error summaries: what happens when it fails"]`}
</FlowDiagram>

The first branch is the one worth defending. Every field you do not ask for is a
field that cannot be mistyped, cannot fail validation, cannot be stored, cannot
leak and does not need a retention policy. In a health product this is not a
performance argument, it is a risk argument.

## Content [#content]

Form copy is owned elsewhere, and this group deliberately does not restate it.
What these pages do specify is *where* each piece of copy goes and *when* the
reader meets it: the label before the control, the hint before the input rather
than after it, the error adjacent to the field and repeated in the summary, and
the unit visible rather than implied.

## Accessibility [#accessibility]

Forms are where WCAG has the most to say and where most of it is
straightforwardly achievable. The success criteria that recur across this group:

* **3.3.2 Labels or Instructions** requires a visible label on every control.
* **3.3.1 Error Identification** requires errors to be identified in text, not
  by colour or an icon alone.
* **3.3.3 Error Suggestion** requires a knowable fix to be offered.
* **3.3.7 Redundant Entry** requires that the reader does not re-enter
  information they have already given in the same process.
* **1.3.5 Identify Input Purpose** requires fields about the reader to carry
  the right `autocomplete` token.
* **4.1.3 Status Messages** requires validation results to reach assistive
  technology without stealing focus.
* **2.5.8 Target Size (Minimum)** requires controls to be at least 24 by 24 CSS
  pixels; opsinjs targets the stricter 44pt floor in
  [Density and touch targets](../../foundations/space/density-and-touch.mdx).

## Research [#research]

<ResearchNote evidence="opinion" date="2026-09-02">
  Separating form design from form wiring is a structural opinion. The evidence
  for it is negative and easy to check: search almost any design system for
  "forms" and you will find a page about a validation library and nothing about
  when the validation should run. Teams then decide that by default, and the
  default is usually "on every keystroke", which is the worst available answer
  for a screen-reader user.

  The individual pages in this group cite what can be cited. WCAG success
  criteria are checkable, and the NHS Digital Service Manual and GOV.UK Design
  System document the one-question-per-page approach in public, which is worth
  reading directly rather than through us. Where we are stating a preference
  rather than a requirement, each page says so in its own Research section.
</ResearchNote>

## Updates to this page [#updates-to-this-page]

<Reviewed />
