Skip to main content

Design system components

FormField

Fieldset/legend wrapper for a group of controls (radio sets, checkbox clusters, composite fields), with a group description and a group-level error. Individual controls own their own label, error and helper text.

Status: stable. Group: form. Markdown version: /design-system/components/form-field.md

When to use

FormField names a group of controls with a fieldset legend: radio sets, checkbox clusters, composite fields like a date range. Individual controls own their own label, error and helperText (the field-wrapper convention), so FormField exists only for the group case, adding a legend, an optional description and a group-level error.

When not to use

  • Do not render a bare <label> + <input> next to a FormField in the same form; the visual rhythm desyncs.
  • Do not wrap a single labeled control; TextInput, Select, Checkbox and friends carry their own label/error/helperText.
  • Do not use it as a layout box; it renders a real fieldset with announcement semantics.
  • Do not use it as a layout box; it renders a real fieldset and its legend is announced on every control inside.

Look-alikes

  • GroupLabel: Do not name a radio or checkbox set with as="label"; a label names one control. Use as="legend" inside a fieldset, or FormField.
  • Label: Do not use Label as a heading above multiple controls; use FormField's group mode or a fieldset legend.

Props

FormField props
PropTypeDefaultDescriptionRequired
legendstringnoneAccessible name for the group of controls, rendered as a <fieldset> <legend>.Yes
childrenReactNodenoneThe grouped controls; each keeps its own label.Yes
groupDescriptionstringnoneHelper text shown under the legend and referenced from the fieldset aria-describedby.No
errorstringnoneGroup-level error message shown after the controls as an alert and referenced from the fieldset aria-describedby.No
requiredbooleannoneAppends a visual asterisk to the legend.No
disabledbooleannoneDisables every contained control via the fieldset.No
hideLegendbooleanfalseKeeps the legend as the group's accessible name but hides it visually, for a group whose visible heading already names it.No
classNamestringnoneMerged onto the fieldset.No

Variants

No variant dimensions and no props with a fixed set of values.

Invalid combinations

The contract records no invalid combinations.

Keyboard

FormField keyboard behaviour
KeyBehaviour
GeneralFormField is not focusable and adds no keyboard behaviour; Tab moves through the contained controls.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

FormField rules
LevelRuleHow it is checked
MUSTARIA: Renders a native <fieldset> whose <legend> (GroupLabel) is the group's accessible name; disabled is applied through the fieldset.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: When error is set, it renders after the controls in a <p role='alert'>.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: The fieldset aria-describedby references groupDescription and then error (each with a generated id), so the group description and group error are announced as the group's description; with neither set, no aria-describedby is rendered.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: When required, the legend asterisk is aria-hidden; each control owns its own aria-required.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDUse it whenever two or more controls answer one question (Notification channels, Opening hours).Not stated in the contract.
SHOULDWrite the legend as the question or category; each control's own label then reads as an answer.Not stated in the contract.
SHOULDUse groupDescription for guidance that applies to the whole set, like Pick at least one.Not stated in the contract.
SHOULDPut cross-control validation in the group error prop; per-control failures belong on the control's own error.Not stated in the contract.
SHOULD NOTDo not wrap a single labeled control; TextInput, Select, Checkbox and friends carry their own label, error and helper text.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { FormField } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/forms exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-form-field>; see the custom elements manifest.