react.form.fieldset
Group related inputs under a legend, with a hint and error message described to the whole group (GOV.UK fieldset).
React ^19 · TypeScript only A React component: use it from a TypeScript project with React installed. Renders on the server; ships no JavaScript of its own.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 10 tests, run in TypeScript.
What it does
Groups inputs that answer one question - an address, a date, a set of radios - under a `<legend>` that asks it. The hint and the error message describe the fieldset itself (`aria-describedby` on the `<fieldset>`), so a screen reader announces them once on entering the group, not again on every input. This is the GOV.UK Design System fieldset; `react.form.radio-group`, `react.form.checkbox-group` and `react.form.date-input` are built on it.
import { Fieldset } from "#fune/react.form.fieldset@^1";
<Fieldset id="address" legend="What is your address?" hint="Your home address" error={errors.address}>
<TextField id="address-line-1" label="Address line 1" autoComplete="address-line1" />
<TextField id="town" label="Town or city" autoComplete="address-level2" />
</Fieldset>
For example
-
a legend and the inputs
<Fieldset id="address" legend="What is your address?" > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">What is your address?</legend>[inputs]</fieldset></div> -
a hint describes the whole group
<Fieldset id="dob" legend="What is your date of birth?" hint="For example, 31 3 1980" > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset" aria-describedby="dob-hint"><legend class="fune-legend">What is your date of birth?</legend><div class="fune-hint" id="dob-hint">For example, 31 3 1980</div>[inputs]</fieldset></div>
The function
Written for React ^19, in TypeScript only. A component: it takes its props and renders HTML, which the tests below pin exactly.
A React capability: React ^19 · TypeScript only. This capability has no Python implementation, so it is shown in TypeScript. A Python project cannot use it: fune build stops and names where it was required. Your choice of Python is kept for every other page.
export function Fieldset(props: FieldsetProps): JSX.Element
Its props, FieldsetProps. A ? marks one the caller may leave out.
| legend | ReactNode | the question the group answers |
| id? | string | names the hint and error: <id>-hint and <id>-error; React's useId() when left out |
| hint? | ReactNode | help under the legend, described to the whole group |
| error? | ReactNode | what is wrong with the group's answer |
| isPageHeading? | boolean | the legend is the page's heading: its text is an h1 inside the legend |
| children? | ReactNode | the inputs |
| className? | string | added to the wrapper's classes |
| renders | JSX.Element |
The type it declares, generated into your project
/** Several inputs that answer one question, such as an address or a date. */
export interface FieldsetProps {
/** the question the group answers */
readonly legend: ReactNode;
/** names the hint and error: <id>-hint and <id>-error; React's useId() when left out */
readonly id?: string;
/** help under the legend, described to the whole group */
readonly hint?: ReactNode;
/** what is wrong with the group's answer */
readonly error?: ReactNode;
/** the legend is the page's heading: its text is an h1 inside the legend */
readonly isPageHeading?: boolean;
/** the inputs */
readonly children?: ReactNode;
/** added to the wrapper's classes */
readonly className?: string;
}
Your code names it in one line, in the file that uses it
import { Fieldset } from "#fune/react.form.fieldset@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { useId, type JSX, type ReactNode } from "react";
import { fieldIds } from "./react_form_form_field.ts"; ← from react.form.form-field ^1.0.0 · built alongside by fune
import type { FieldsetProps } from "./react_form_fieldset_types.ts";
function present(node: ReactNode): boolean {
return node !== null && node !== undefined && node !== false && node !== "";
}
/**
* The GOV.UK fieldset: a legend that asks the question, then the hint and the
* error message, then the inputs. The hint and error describe the fieldset
* itself, so a screen reader announces them once on entering the group
* rather than on every input. With isPageHeading the legend holds the page's
* h1, the pattern for a page that asks one question.
*/
export function Fieldset(props: FieldsetProps): JSX.Element {
const auto = useId();
const { legend, hint, error, isPageHeading, children, className } = props;
const id = props.id ?? auto;
const ids = fieldIds(id, present(hint), present(error));
const classes = ["fune-field", ids.error ? "fune-field--error" : null, className || null].filter(Boolean).join(" ");
return (
<div className={classes}>
<fieldset className="fune-fieldset" aria-describedby={ids.describedBy ?? undefined}>
<legend className={isPageHeading ? "fune-legend fune-legend--heading" : "fune-legend"}>
{isPageHeading ? <h1 className="fune-legend-heading">{legend}</h1> : legend}
</legend>
{ids.hint ? (
<div className="fune-hint" id={ids.hint}>
{hint}
</div>
) : null}
{ids.error ? (
<p className="fune-error-message" id={ids.error}>
<span className="fune-visually-hidden">Error:</span> {error}
</p>
) : null}
{children}
</fieldset>
</div>
);
}Install
npm install react react-dom
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project) with React ^19 installed (Functional Weave does not ship it, and fune build stops with the npm install line if it is missing), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add react.form.fieldset
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./react.form.fieldset-1.0.0-typescript.fune, or fetch it from a terminal with fune pull react.form.fieldset@1.0.0:typescript.
It is React ^19 · TypeScript only, so there is no package for Python or Rust. The full package is one file too: react.form.fieldset-1.0.0.fune, 10,589 bytes, sha256 6840a5baf9ec13279943d7713fd08fd81629f3176c0f9bd0ac678d0fe7f70f04.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before react.form.fieldset
after — your function gets the result and the arguments, and returns the final result.
// fune: after react.form.fieldset
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace react.form.form-field in react.form.fieldset
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show react.form.fieldset --steps.
// fune: step react.form.fieldset after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript alone, with the React in tooling/react, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json. A component’s tests are render tests: the props go in, and the HTML renderToStaticMarkup makes of them has to match exactly; a React warning during the render fails the test.
-
a legend and the inputs
<Fieldset id="address" legend="What is your address?" > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">What is your address?</legend>[inputs]</fieldset></div> -
a hint describes the whole group
<Fieldset id="dob" legend="What is your date of birth?" hint="For example, 31 3 1980" > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset" aria-describedby="dob-hint"><legend class="fune-legend">What is your date of birth?</legend><div class="fune-hint" id="dob-hint">For example, 31 3 1980</div>[inputs]</fieldset></div> -
an error marks the group and describes it
<Fieldset id="contact" legend="How would you like to be contacted?" error="Select how you would like to be contacted" > [inputs] </Fieldset>renders
<div class="fune-field fune-field--error"><fieldset class="fune-fieldset" aria-describedby="contact-error"><legend class="fune-legend">How would you like to be contacted?</legend><p class="fune-error-message" id="contact-error"><span class="fune-visually-hidden">Error:</span> Select how you would like to be contacted</p>[inputs]</fieldset></div> -
hint then error, both described, hint first
<Fieldset id="dob" legend="Date of birth" hint="For example, 31 3 1980" error="Date of birth must be in the past" > [inputs] </Fieldset>renders
<div class="fune-field fune-field--error"><fieldset class="fune-fieldset" aria-describedby="dob-hint dob-error"><legend class="fune-legend">Date of birth</legend><div class="fune-hint" id="dob-hint">For example, 31 3 1980</div><p class="fune-error-message" id="dob-error"><span class="fune-visually-hidden">Error:</span> Date of birth must be in the past</p>[inputs]</fieldset></div> -
the legend as the page heading holds an h1
<Fieldset id="where" legend="Where do you live?" isPageHeading > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend fune-legend--heading"><h1 class="fune-legend-heading">Where do you live?</h1></legend>[inputs]</fieldset></div> -
isPageHeading false is an ordinary legend
<Fieldset id="where" legend="Where do you live?" isPageHeading={false} />renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Where do you live?</legend></fieldset></div> -
a class name joins the wrapper's, after the error class
<Fieldset id="a" legend="Address" error="Enter your address" className="fune-field--wide" />renders
<div class="fune-field fune-field--error fune-field--wide"><fieldset class="fune-fieldset" aria-describedby="a-error"><legend class="fune-legend">Address</legend><p class="fune-error-message" id="a-error"><span class="fune-visually-hidden">Error:</span> Enter your address</p></fieldset></div> -
empty hint and null error show nothing
<Fieldset id="a" legend="Address" hint="" error={null} > [inputs] </Fieldset>renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Address</legend>[inputs]</fieldset></div> -
the legend is escaped
<Fieldset id="a" legend="Terms & <conditions>" />renders
<div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Terms & <conditions></legend></fieldset></div> -
an id with a space is refused
<Fieldset id="home address" legend="Address" />error: a field id cannot contain spaces
More from the author
`isPageHeading` puts the legend's text in an `<h1>`, for a page that asks one question: the question is then both the page's heading and the group's name, as GOV.UK recommends, rather than two headings saying the same thing.
The id names the hint and the error, `<id>-hint` and `<id>-error` (see `react.form.form-field`); the `<fieldset>` itself has no id, as in GOV.UK, because the group's controls own it: the first radio or checkbox is `<id>`, a date's inputs `<id>-day` and so on, and that is what an error summary links to. Left out, the id is React's `useId()`. It holds no state, so it is not a client component and renders on the server.
Classes: `fune-field` (and `fune-field--error`), `fune-fieldset`, `fune-legend` (and `fune-legend--heading`), `fune-legend-heading`, `fune-hint`, `fune-error-message`, `fune-visually-hidden`.
Source: GOV.UK Design System, "Fieldset" https://design-system.service.gov.uk/components/fieldset/ and "Legends as page headings" https://design-system.service.gov.uk/get-started/labels-legends-headings/.
Files
| Path | Bytes |
|---|---|
| README.md | 1,864 |
| impl/typescript.tsx | 1,753 |
| vectors.json | 3,850 |