GitHub

Form section

A titled group of self-labelling controls that ends in an action row — the unit intake forms, settings panels, and dialog bodies are assembled from.

When to use: Any screen that collects more than a couple of values — intake forms, settings panels, dialog bodies. One section per topic; stack sections to build a full form.

Default

A project-details section from an environmental review intake. Each control carries its own label, help-text and required state — that is what wires the name to the input inside its shadow root, and it is the whole field chrome, so no wrapper is involved. Short, related fields (lead agency / county) pair on one row; everything else runs single column. The action row closes the section: primary submit first, ghost cancel beside it.

Project details

Tell us about the project under review. Fields marked * are required.

Validation state

The same section after a failed submit: the name is missing and no pathway is selected. Two things happen, and both are needed.

An esa-error-summary goes above the form and takes focus. Without it, pressing Submit appears to do nothing: focus is on the button at the bottom, the errors are added off-screen above, and a screen reader — which is on one element at a time — is told nothing at all. It is also what a page-zoom user, seeing only the region around the button, gets instead of silence.

Then every invalid control takes error-text. One attribute reddens the border, sets aria-invalid, points aria-describedby at the message, and renders it with an icon and a visually-hidden "Error:" so the line is not distinguished from a help line by colour alone. Option groups take it too now. Word the summary and the inline message the same.

Project details

Tell us about the project under review. Fields marked * are required.

Composition notes

  • Self-chromed controls label themselves. esa-text-field, esa-textarea and the other Lit controls render into shadow DOM, so a <label> outside them can never be associated with the input inside — esa-form-field's label is a visual caption only, and a control wrapped in it with its own label unset has no accessible name. Set the control's own label / help-text and leave the wrapper's unset. Reach for esa-form-field's label only around a native <input> you can wire a for/id pair to yourself, or around a group that has no label of its own.
  • A failed submit needs an esa-error-summary, not just red fields. Focus is on the Submit button when the form fails; the errors appear above it, off-screen, and nothing announces them. The summary is the thing that takes focus and says how many problems there are and where each one is. Set its errors and call focus() in the same handler that sets each field's error-text, and word the two the same.
  • Error messages live in components, never ad-hoc styled spans. Every control that collects a value now takes error-text — including esa-radio-group and esa-checkbox-group, which previously had no error channel at all. esa-field-error is the escape hatch for a server-rendered message that is already on the page at load.
  • The error no longer replaces help text — both render, and aria-describedby announces the error first, then the hint. A format requirement is most needed at the moment the format was got wrong, and the old either/or deleted it exactly then.
  • The action row belongs to the section, not to any field: primary action first, ghost cancel second, left-aligned on the same axis as the labels, separated from the fields by a divider.
  • Single column by default. Only pair short, related fields on one row, and collapse the pair on narrow viewports. Controls run full width — the section's max-width sets the measure, not the individual control.
  • One size across the section: everything defaults to md on the shared scale, so inputs, selects, and buttons share heights and line up when they meet on a row.