react.form.checkbox
One checkbox with its label beside it, such as "I agree to the terms" (GOV.UK single checkbox).
React ^19 · TypeScript only A React component: use it from a TypeScript project with React installed. A client component ("use client").
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 14 tests, run in TypeScript.
What it does
One checkbox on its own, with its label to the right of the box: "I agree to the terms", "Remember me on this device", "Send me news by email". It is the GOV.UK Design System checkbox used to toggle a single option on or off, with an optional hint under the label and an error message above the box.
"use client";
import { Checkbox } from "#fune/react.form.checkbox@^1";
<Checkbox id="terms" label="I agree to the terms and conditions" required
error={errors.terms} checked={agreed} onChange={setAgreed} />
For example
-
a checkbox with its label to the right, submitting yes
<Checkbox id="terms" label="I agree to the terms and conditions" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="terms" type="checkbox" name="terms" value="yes"/><label class="fune-label fune-checkbox-label" for="terms">I agree to the terms and conditions</label></div></div> -
a name and value of its own
<Checkbox id="c1" name="marketing" value="opt-in" label="Send me news by email" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="c1" type="checkbox" name="marketing" value="opt-in"/><label class="fune-label fune-checkbox-label" for="c1">Send me news by email</label></div></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.
export function Checkbox(props: CheckboxProps): JSX.Element
Its props, CheckboxProps. A ? marks one the caller may leave out.
| label | ReactNode | what ticking it means, shown to the right of the box |
| id? | string | the checkbox's id; React's useId() when left out |
| name? | string | what it is submitted as; the id when left out |
| value? | string | what is submitted when it is ticked; yes when left out |
| checked? | boolean | whether it is ticked, for a controlled checkbox (with onChange) |
| defaultChecked? | boolean | whether it starts ticked, for an uncontrolled one |
| onChange? | (value: boolean) => void | called with true when it is ticked and false when it is cleared |
| onBlur? | () => void | called when the checkbox loses focus, to mark the field touched |
| hint? | ReactNode | help under the label |
| error? | ReactNode | what is wrong; marks the checkbox invalid and describes it |
| required? | boolean | the browser refuses the form until it is ticked |
| disabled? | boolean | shown but not changeable, and not submitted |
| className? | string | added to the wrapper's classes |
| renders | JSX.Element |
The type it declares, generated into your project
/** One thing a person turns on or off, such as agreeing to the terms. */
export interface CheckboxProps {
/** what ticking it means, shown to the right of the box */
readonly label: ReactNode;
/** the checkbox's id; React's useId() when left out */
readonly id?: string;
/** what it is submitted as; the id when left out */
readonly name?: string;
/** what is submitted when it is ticked; yes when left out */
readonly value?: string;
/** whether it is ticked, for a controlled checkbox (with onChange) */
readonly checked?: boolean;
/** whether it starts ticked, for an uncontrolled one */
readonly defaultChecked?: boolean;
/** called with true when it is ticked and false when it is cleared */
readonly onChange?: (value: boolean) => void;
/** called when the checkbox loses focus, to mark the field touched */
readonly onBlur?: () => void;
/** help under the label */
readonly hint?: ReactNode;
/** what is wrong; marks the checkbox invalid and describes it */
readonly error?: ReactNode;
/** the browser refuses the form until it is ticked */
readonly required?: boolean;
/** shown but not changeable, and not submitted */
readonly disabled?: boolean;
/** added to the wrapper's classes */
readonly className?: string;
}
Your code names it in one line, in the file that uses it
import { Checkbox } from "#fune/react.form.checkbox@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
"use client";
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 { CheckboxProps } from "./react_form_checkbox_types.ts";
function present(node: ReactNode): boolean {
return node !== null && node !== undefined && node !== false && node !== "";
}
/**
* One GOV.UK checkbox on its own: the error message above, then the box with
* its label to the right and its hint under the label. It cannot use
* FormField, whose label comes before the control, so it takes the same ids
* from fieldIds. Unlike a checkbox in a group, it is a control of its own, so
* the error describes it and marks it aria-invalid.
*/
export function Checkbox(props: CheckboxProps): JSX.Element {
const auto = useId();
const { label, value, checked, defaultChecked, onChange, onBlur, hint, error, required, disabled, 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}>
{ids.error ? (
<p className="fune-error-message" id={ids.error}>
<span className="fune-visually-hidden">Error:</span> {error}
</p>
) : null}
<div className="fune-checkbox">
<input
className="fune-checkbox-input"
id={ids.control}
name={props.name ?? id}
type="checkbox"
value={value ?? "yes"}
checked={checked}
defaultChecked={defaultChecked}
required={required}
disabled={disabled}
aria-describedby={ids.describedBy ?? undefined}
aria-invalid={ids.error ? true : undefined}
onBlur={onBlur ? () => onBlur() : undefined}
onChange={(event) => onChange?.(event.target.checked)}
/>
<label className="fune-label fune-checkbox-label" htmlFor={ids.control}>
{label}
</label>
{ids.hint ? (
<div className="fune-hint fune-checkbox-hint" id={ids.hint}>
{hint}
</div>
) : null}
</div>
</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.checkbox
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./react.form.checkbox-1.0.1-typescript.fune, or fetch it from a terminal with fune pull react.form.checkbox@1.0.1: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.checkbox-1.0.1.fune, 17,571 bytes, sha256 12202cc73d1122e81fe87afe337ff39422336f629324450d07cadd9278b372ca.
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.checkbox
after — your function gets the result and the arguments, and returns the final result.
// fune: after react.form.checkbox
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.checkbox
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.checkbox --steps.
// fune: step react.form.checkbox 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 checkbox with its label to the right, submitting yes
<Checkbox id="terms" label="I agree to the terms and conditions" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="terms" type="checkbox" name="terms" value="yes"/><label class="fune-label fune-checkbox-label" for="terms">I agree to the terms and conditions</label></div></div> -
a name and value of its own
<Checkbox id="c1" name="marketing" value="opt-in" label="Send me news by email" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="c1" type="checkbox" name="marketing" value="opt-in"/><label class="fune-label fune-checkbox-label" for="c1">Send me news by email</label></div></div> -
controlled and ticked renders checked
<Checkbox id="terms" label="I agree" checked />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="terms" type="checkbox" name="terms" checked="" value="yes"/><label class="fune-label fune-checkbox-label" for="terms">I agree</label></div></div> -
controlled and clear renders no checked attribute
<Checkbox id="terms" label="I agree" checked={false} />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="terms" type="checkbox" name="terms" value="yes"/><label class="fune-label fune-checkbox-label" for="terms">I agree</label></div></div> -
defaultChecked renders checked too
<Checkbox id="remember" label="Remember me on this device" defaultChecked />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="remember" type="checkbox" name="remember" checked="" value="yes"/><label class="fune-label fune-checkbox-label" for="remember">Remember me on this device</label></div></div> -
a hint goes under the label and describes the checkbox
<Checkbox id="remember" label="Remember me" hint="Do not tick this on a shared computer" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="remember" type="checkbox" aria-describedby="remember-hint" name="remember" value="yes"/><label class="fune-label fune-checkbox-label" for="remember">Remember me</label><div class="fune-hint fune-checkbox-hint" id="remember-hint">Do not tick this on a shared computer</div></div></div> -
an error goes above the box, marks it invalid and describes it
<Checkbox id="terms" label="I agree to the terms" error="Agree to the terms to continue" />renders
<div class="fune-field fune-field--error"><p class="fune-error-message" id="terms-error"><span class="fune-visually-hidden">Error:</span> Agree to the terms to continue</p><div class="fune-checkbox"><input class="fune-checkbox-input" id="terms" type="checkbox" aria-describedby="terms-error" aria-invalid="true" name="terms" value="yes"/><label class="fune-label fune-checkbox-label" for="terms">I agree to the terms</label></div></div> -
hint and error: described by both, hint first
<Checkbox id="t" label="I agree" hint="You can read the terms first" error="Agree to the terms" checked={false} />renders
<div class="fune-field fune-field--error"><p class="fune-error-message" id="t-error"><span class="fune-visually-hidden">Error:</span> Agree to the terms</p><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" aria-describedby="t-hint t-error" aria-invalid="true" name="t" value="yes"/><label class="fune-label fune-checkbox-label" for="t">I agree</label><div class="fune-hint fune-checkbox-hint" id="t-hint">You can read the terms first</div></div></div> -
required and disabled are boolean attributes
<Checkbox id="t" label="I agree" required disabled />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" required="" disabled="" name="t" value="yes"/><label class="fune-label fune-checkbox-label" for="t">I agree</label></div></div> -
false booleans, an empty hint and a null error render nothing
<Checkbox id="t" label="I agree" required={false} disabled={false} hint="" error={null} className="fune-field--compact" />renders
<div class="fune-field fune-field--compact"><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" name="t" value="yes"/><label class="fune-label fune-checkbox-label" for="t">I agree</label></div></div>
Show the other 4 tests
-
an error and a class name: the error class comes first
<Checkbox id="t" label="I agree" error="Agree to continue" className="wide" />renders
<div class="fune-field fune-field--error wide"><p class="fune-error-message" id="t-error"><span class="fune-visually-hidden">Error:</span> Agree to continue</p><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" aria-describedby="t-error" aria-invalid="true" name="t" value="yes"/><label class="fune-label fune-checkbox-label" for="t">I agree</label></div></div> -
label and value are escaped
<Checkbox id="t" label="Terms & <conditions>" value="a\"b" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" name="t" value="a"b"/><label class="fune-label fune-checkbox-label" for="t">Terms & <conditions></label></div></div> -
an empty value is submitted as empty, not yes
<Checkbox id="t" label="I agree" value="" />renders
<div class="fune-field"><div class="fune-checkbox"><input class="fune-checkbox-input" id="t" type="checkbox" name="t" value=""/><label class="fune-label fune-checkbox-label" for="t">I agree</label></div></div> -
an id with a space is refused
<Checkbox id="the terms" label="I agree" />error: a field id cannot contain spaces
More from the author
- **onChange** is called with `true` when the box is ticked and `false` when it is cleared, not with the event. - **checked** with **onChange** is a controlled checkbox; **defaultChecked** alone is an uncontrolled one, fine for a plain `<form>` post. Do not pass both. - **onBlur** is called, with nothing, when the box loses focus, so a form can mark the field touched (`form.state`'s blur action). - **value** is what the form posts when the box is ticked. It is `yes` when left out, rather than the browser's `on`, so a server reads `terms=yes`; nothing at all is posted when the box is clear. `""` is kept as `""`. - **id** is the checkbox's id and, unless **name** says otherwise, the name it is posted under. Left out, it is React's `useId()`; pass one whenever an error summary has to link to the box.
Why not `FormField`: its label comes before the control, and a checkbox's label goes after the box, so this component takes the same ids from `fieldIds` (`<id>-hint`, `<id>-error`, described hint first) and lays them out itself. Unlike a box in a `react.form.checkbox-group`, a lone checkbox is a control of its own, so the error describes the input and marks it `aria-invalid="true"`. The hint goes under the label, where GOV.UK puts an item hint.
For several related choices use `react.form.checkbox-group`; for a yes or no answer that must be given either way, use `react.form.radio-group` with Yes and No, since an unticked box cannot tell "no" from "did not answer".
It is a client component (it listens for changes), so the built file starts with `"use client"`.
Classes: `fune-field` (and `fune-field--error`), `fune-error-message`, `fune-visually-hidden`, `fune-checkbox` (the box and its label), `fune-checkbox-input`, `fune-label fune-checkbox-label`, `fune-hint fune-checkbox-hint`.
Sources: GOV.UK Design System, "Checkboxes" (toggling a single option, item hints) https://design-system.service.gov.uk/components/checkboxes/ and "Error message" https://design-system.service.gov.uk/components/error-message/.
## Notices
Portions derived from GOV.UK Frontend (https://github.com/alphagov/govuk-frontend), Copyright (c) 2017 Crown Copyright (Government Digital Service), under the MIT License; the full notice is in NOTICE.
1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 1,217 |
| README.md | 2,896 |
| impl/typescript.tsx | 2,207 |
| vectors.json | 6,415 |