Functional Weave
Code in TypeScript

react.form.checkbox-group

A question answered by ticking any of several checkboxes, with item hints and an exclusive "None" (GOV.UK checkboxes).

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 24 tests, run in TypeScript.toggleValue 12 · CheckboxGroup 12

What it does

A question answered by ticking any number of checkboxes - "Which types of waste do you transport? Select all that apply" - inside a fieldset whose legend asks it. It is the GOV.UK Design System checkboxes component: item hints, an "or" divider and an exclusive "None of these" option included. `toggleValue` is the list arithmetic behind every click, a pure function you can also use on the server.

"use client";
import { CheckboxGroup } from "#fune/react.form.checkbox-group@^1";

<CheckboxGroup id="countries" legend="Will you travel to any of these countries?"
  hint="Select all that apply" error={errors.countries}
  values={countries} onChange={setCountries}
  options={[
    { value: "france", label: "France" },
    { value: "portugal", label: "Portugal" },
    { value: "spain", label: "Spain" },
    { value: "none", label: "No, I will not be travelling to any of these countries", divider: "or", exclusive: true },
  ]} />

The functions

A group: 2 functions that work together, each in its own file, each pinned by its own tests, written for React ^19. A project can install only the ones it calls.

  1. toggleValue (options: CheckboxOption[], values: string[], value: string, checked: bool) -> string[]
  2. CheckboxGroup (props: CheckboxGroupProps) -> element

The types it declares, generated into your project

/** One checkbox in a group. */
export interface CheckboxOption {
  /** what is submitted when it is ticked */
  readonly value: string;
  /** shown to the right of the box */
  readonly label: ReactNode;
  /** help under the label, describing this checkbox only */
  readonly hint?: ReactNode;
  /** shown but not changeable */
  readonly disabled?: boolean;
  /** shown between this option and the one before, such as "or" */
  readonly divider?: ReactNode;
  /** ticking it clears every other option, and ticking another clears it: "None of these" */
  readonly exclusive?: boolean;
}

/** A question with several answers, any number of which may be ticked. */
export interface CheckboxGroupProps {
  /** the question the group asks */
  readonly legend: ReactNode;
  /** the checkboxes, in order */
  readonly options: readonly CheckboxOption[];
  /** the first checkbox's id; the others are <id>-2, <id>-3 ...; React's useId() when left out */
  readonly id?: string;
  /** what every checkbox is submitted as; the id when left out */
  readonly name?: string;
  /** the ticked values, for a controlled group (with onChange) */
  readonly values?: readonly string[];
  /** the values ticked at first, for an uncontrolled one */
  readonly defaultValues?: readonly string[];
  /** called with every ticked value, in the options' order, after each change */
  readonly onChange?: (value: readonly string[]) => void;
  /** called when any checkbox in the group loses focus, to mark the field touched */
  readonly onBlur?: () => void;
  /** help under the legend, such as "Select all that apply" */
  readonly hint?: ReactNode;
  /** what is wrong with the answer; describes the whole group */
  readonly error?: ReactNode;
  /** the legend is the page's heading */
  readonly isPageHeading?: boolean;
  /** smaller checkboxes, for a filter or a dense page */
  readonly small?: boolean;
  /** every checkbox is shown but not changeable */
  readonly disabled?: boolean;
  /** added to the wrapper's classes */
  readonly className?: string;
}

Once installed, your code imports each one from the group's module.

toggleValue throws on bad input 12 tests

export function toggleValue(options: readonly CheckboxOption[], values: readonly string[], value: string, checked: boolean): readonly string[]
optionsCheckboxOption[]the group's options, in the order they are shown
valuesstring[]the values ticked before the change
valuestringthe option that was ticked or cleared
checkedbooltrue when it was ticked, false when it was cleared
returnsstring[]the values ticked after the change, in the options' order, each once

For example

  • toggleValue(options ×3, , a, true) → a ticking the first box
  • toggleValue(options ×3, c, a, true) → a, c ticking keeps the options' order, not the click order
  • toggleValue(options ×3, a, b, c, b, false) → a, c clearing a box removes only it
import { toggleValue } from "#fune/react.form.checkbox-group@^1";
impl/typescript/toggle_value.ts · 27 lines · open · raw
import type { CheckboxOption } from "./react_form_checkbox_group_types.ts";

/**
 * The ticked values after one checkbox changes. The answer is always in the
 * options' order, whatever order the boxes were ticked in, and holds each
 * value once, so a form posts the same list for the same ticks. A value that
 * is not an option any more (the options changed) drops out. An exclusive
 * option ("None of these") cannot be ticked alongside anything else: ticking
 * it clears the rest, and ticking another clears it (GOV.UK's
 * data-behaviour="exclusive").
 */
export function toggleValue(options: readonly CheckboxOption[], values: readonly string[], value: string, checked: boolean): readonly string[] {
  const option = options.find((o) => o.value === value);
  if (option === undefined) throw new Error(`"${value}" is not one of the checkbox options`);
  let next: Set<string>;
  if (!checked) {
    next = new Set(values.filter((v) => v !== value));
  } else if (option.exclusive) {
    next = new Set([value]);
  } else {
    const exclusive = new Set(options.filter((o) => o.exclusive).map((o) => o.value));
    next = new Set([...values.filter((v) => !exclusive.has(v)), value]);
  }
  const out: string[] = [];
  for (const o of options) if (next.has(o.value) && !out.includes(o.value)) out.push(o.value);
  return out;
}

CheckboxGroup 12 tests

export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element

Its props, CheckboxGroupProps. A ? marks one the caller may leave out.

legendReactNodethe question the group asks
optionsreadonly CheckboxOption[]the checkboxes, in order
id?stringthe first checkbox's id; the others are <id>-2, <id>-3 ...; React's useId() when left out
name?stringwhat every checkbox is submitted as; the id when left out
values?readonly string[]the ticked values, for a controlled group (with onChange)
defaultValues?readonly string[]the values ticked at first, for an uncontrolled one
onChange?(value: readonly string[]) => voidcalled with every ticked value, in the options' order, after each change
onBlur?() => voidcalled when any checkbox in the group loses focus, to mark the field touched
hint?ReactNodehelp under the legend, such as "Select all that apply"
error?ReactNodewhat is wrong with the answer; describes the whole group
isPageHeading?booleanthe legend is the page's heading
small?booleansmaller checkboxes, for a filter or a dense page
disabled?booleanevery checkbox is shown but not changeable
className?stringadded to the wrapper's classes
rendersJSX.Element

For example

  1. a legend and three checkboxes: the first takes the id, the rest -2 and -3

    <CheckboxGroup
      id="waste"
      legend="Which types of waste do you transport?"
      options={[{"value":"carcasses","label":"Waste from animal carcasses"},{"value":"mines","label":"Waste from mines or quarries"},{"value":"farm","label":"Farm or agricultural waste"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Which types of waste do you transport?</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste" type="checkbox" name="waste" value="carcasses"/><label class="fune-label fune-checkbox-label" for="waste">Waste from animal carcasses</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste-2" type="checkbox" name="waste" value="mines"/><label class="fune-label fune-checkbox-label" for="waste-2">Waste from mines or quarries</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste-3" type="checkbox" name="waste" value="farm"/><label class="fune-label fune-checkbox-label" for="waste-3">Farm or agricultural waste</label></div></div></fieldset></div>
  2. controlled values tick their boxes

    <CheckboxGroup
      id="c"
      name="colours"
      legend="Colours"
      values={["blue"]}
      options={[{"value":"red","label":"Red"},{"value":"blue","label":"Blue"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Colours</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="c" type="checkbox" name="colours" value="red"/><label class="fune-label fune-checkbox-label" for="c">Red</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="c-2" type="checkbox" name="colours" checked="" value="blue"/><label class="fune-label fune-checkbox-label" for="c-2">Blue</label></div></div></fieldset></div>
import { CheckboxGroup } from "#fune/react.form.checkbox-group@^1";
impl/typescript/checkbox_group.tsx · 77 lines · open · raw

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 { Fragment, useId, useState, type JSX, type ReactNode } from "react";
import { Fieldset } from "./react_form_fieldset.ts";  ← from react.form.fieldset ^1.0.0 · built alongside by fune
import { toggleValue } from "./react_form_checkbox_group_toggle_value.ts";  ← toggleValue, another function of this group · built into the same file, even by a slim install
import type { CheckboxGroupProps } from "./react_form_checkbox_group_types.ts";

function present(node: ReactNode): boolean {
  return node !== null && node !== undefined && node !== false && node !== "";
}

/**
 * GOV.UK checkboxes: a fieldset whose legend asks the question, its hint and
 * error describing the whole group, then one checkbox per option. The first
 * checkbox takes the id, so an error summary's link lands on it, and the
 * rest are <id>-2, <id>-3...; the group's hint and error are <id>-hint and
 * <id>-error. Every box is controlled from one list,
 * held here when the caller passes defaultValues, so onChange always gets
 * the whole list from toggleValue.
 */
export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element {
  const auto = useId();
  const { legend, options, values, defaultValues, onChange, onBlur, hint, error, isPageHeading, small, disabled, className } = props;
  const id = props.id ?? auto;
  const name = props.name ?? id;
  const [own, setOwn] = useState<readonly string[]>(defaultValues ?? []);
  if (options.length === 0) throw new Error("a checkbox group needs at least one option");
  const seen = new Set<string>();
  for (const o of options) {
    if (seen.has(o.value)) throw new Error(`checkbox option values must be unique: "${o.value}" is there twice`);
    seen.add(o.value);
  }
  const current = values ?? own;
  const change = (value: string, checked: boolean) => {
    const next = toggleValue(options, current, value, checked);
    if (values === undefined) setOwn(next);
    onChange?.(next);
  };
  return (
    <Fieldset id={id} legend={legend} hint={hint} error={error} isPageHeading={isPageHeading} className={className}>
      <div className={small ? "fune-checkboxes fune-checkboxes--small" : "fune-checkboxes"}>
        {options.map((o, i) => {
          const itemId = i === 0 ? id : `${id}-${i + 1}`;
          const hintId = present(o.hint) ? `${itemId}-item-hint` : undefined;
          return (
            <Fragment key={o.value}>
              {present(o.divider) ? <div className="fune-checkboxes-divider">{o.divider}</div> : null}
              <div className="fune-checkbox">
                <input
                  className="fune-checkbox-input"
                  id={itemId}
                  name={name}
                  type="checkbox"
                  value={o.value}
                  checked={current.includes(o.value)}
                  disabled={disabled || o.disabled}
                  aria-describedby={hintId}
                  data-behaviour={o.exclusive ? "exclusive" : undefined}
                  onBlur={onBlur ? () => onBlur() : undefined}
                  onChange={(event) => change(o.value, event.target.checked)}
                />
                <label className="fune-label fune-checkbox-label" htmlFor={itemId}>
                  {o.label}
                </label>
                {hintId ? (
                  <div className="fune-hint fune-checkbox-hint" id={hintId}>
                    {o.hint}
                  </div>
                ) : null}
              </div>
            </Fragment>
          );
        })}
      </div>
    </Fieldset>
  );
}

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-group

That builds the whole group. To build only what you call, and whatever it uses inside the group:

fune add react.form.checkbox-group --only toggleValue
Download for TypeScript react.form.checkbox-group-1.0.1-typescript.fune · 30,567 bytes sha256 38b7c36e2c345d248ff9b5661acaac0da40eae3e8a70071a86e7cacf23b13a65

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./react.form.checkbox-group-1.0.1-typescript.fune, or fetch it from a terminal with fune pull react.form.checkbox-group@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-group-1.0.1.fune, 30,539 bytes, sha256 963bedc570f4f5372600ea3f65c2cfd809c700eb96f382a75842021a62ee99a7.

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-group.toggleValue
// fune: before react.form.checkbox-group.CheckboxGroup

after — your function gets the result and the arguments, and returns the final result.

// fune: after react.form.checkbox-group.toggleValue
// fune: after react.form.checkbox-group.CheckboxGroup

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.fieldset in react.form.checkbox-group

step — your function runs at a numbered point inside a 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-group --steps.

// fune: step react.form.checkbox-group.<fn> 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.

toggleValue 12 tests

CaseArgumentsExpected
ticking the first box options ×3, , a, true → a
ticking keeps the options' order, not the click order options ×3, c, a, true → a, c
clearing a box removes only it options ×3, a, b, c, b, false → a, c
clearing the last box leaves an empty list options ×2, b, b, false →
ticking a box already ticked lists it once options ×2, b, a, a, true → a, b
clearing a box that was not ticked only puts the list in order options ×3, c, a, b, false → a, c
duplicates and values no longer offered drop out options ×2, b, old, b, a, false → b
ticking the exclusive None clears every other box options ×3, france, spain, none, true → none
ticking another box clears the exclusive None options ×3, none, spain, true → spain
clearing the exclusive None leaves nothing ticked options ×2, none, none, false →
Show the other 2 tests
CaseArgumentsExpected
exclusive false is an ordinary option options ×2, a, b, true → a, b
a value that is not an option is refused options ×1, a, z, true → error: "z" is not one of the checkbox options

CheckboxGroup 12 tests

  1. a legend and three checkboxes: the first takes the id, the rest -2 and -3

    <CheckboxGroup
      id="waste"
      legend="Which types of waste do you transport?"
      options={[{"value":"carcasses","label":"Waste from animal carcasses"},{"value":"mines","label":"Waste from mines or quarries"},{"value":"farm","label":"Farm or agricultural waste"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Which types of waste do you transport?</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste" type="checkbox" name="waste" value="carcasses"/><label class="fune-label fune-checkbox-label" for="waste">Waste from animal carcasses</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste-2" type="checkbox" name="waste" value="mines"/><label class="fune-label fune-checkbox-label" for="waste-2">Waste from mines or quarries</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="waste-3" type="checkbox" name="waste" value="farm"/><label class="fune-label fune-checkbox-label" for="waste-3">Farm or agricultural waste</label></div></div></fieldset></div>
  2. controlled values tick their boxes

    <CheckboxGroup
      id="c"
      name="colours"
      legend="Colours"
      values={["blue"]}
      options={[{"value":"red","label":"Red"},{"value":"blue","label":"Blue"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Colours</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="c" type="checkbox" name="colours" value="red"/><label class="fune-label fune-checkbox-label" for="c">Red</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="c-2" type="checkbox" name="colours" checked="" value="blue"/><label class="fune-label fune-checkbox-label" for="c-2">Blue</label></div></div></fieldset></div>
  3. default values tick their boxes too

    <CheckboxGroup
      id="c"
      legend="Colours"
      defaultValues={["red","blue"]}
      options={[{"value":"red","label":"Red"},{"value":"blue","label":"Blue"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Colours</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="c" type="checkbox" name="c" checked="" value="red"/><label class="fune-label fune-checkbox-label" for="c">Red</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="c-2" type="checkbox" name="c" checked="" value="blue"/><label class="fune-label fune-checkbox-label" for="c-2">Blue</label></div></div></fieldset></div>
  4. a hint describes the group; an item hint describes its own box

    <CheckboxGroup
      id="nationality"
      legend="What is your nationality?"
      hint="Select all that apply"
      options={[{"value":"british","label":"British","hint":"including English, Scottish, Welsh and Northern Irish"},{"value":"irish","label":"Irish"}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset" aria-describedby="nationality-hint"><legend class="fune-legend">What is your nationality?</legend><div class="fune-hint" id="nationality-hint">Select all that apply</div><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="nationality" type="checkbox" aria-describedby="nationality-item-hint" name="nationality" value="british"/><label class="fune-label fune-checkbox-label" for="nationality">British</label><div class="fune-hint fune-checkbox-hint" id="nationality-item-hint">including English, Scottish, Welsh and Northern Irish</div></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="nationality-2" type="checkbox" name="nationality" value="irish"/><label class="fune-label fune-checkbox-label" for="nationality-2">Irish</label></div></div></fieldset></div>
  5. an error describes the fieldset, not each box

    <CheckboxGroup
      id="n"
      legend="Nationality"
      error="Select your nationality"
      options={[{"value":"british","label":"British"},{"value":"irish","label":"Irish"}]}
    />

    renders

    <div class="fune-field fune-field--error"><fieldset class="fune-fieldset" aria-describedby="n-error"><legend class="fune-legend">Nationality</legend><p class="fune-error-message" id="n-error"><span class="fune-visually-hidden">Error:</span> Select your nationality</p><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="n" type="checkbox" name="n" value="british"/><label class="fune-label fune-checkbox-label" for="n">British</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="n-2" type="checkbox" name="n" value="irish"/><label class="fune-label fune-checkbox-label" for="n-2">Irish</label></div></div></fieldset></div>
  6. an exclusive None after an or divider

    <CheckboxGroup
      id="countries"
      legend="Will you travel to any of these countries?"
      values={["none"]}
      options={[{"value":"france","label":"France"},{"value":"none","label":"No, none of these","divider":"or","exclusive":true}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Will you travel to any of these countries?</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="countries" type="checkbox" name="countries" value="france"/><label class="fune-label fune-checkbox-label" for="countries">France</label></div><div class="fune-checkboxes-divider">or</div><div class="fune-checkbox"><input class="fune-checkbox-input" id="countries-2" type="checkbox" data-behaviour="exclusive" name="countries" checked="" value="none"/><label class="fune-label fune-checkbox-label" for="countries-2">No, none of these</label></div></div></fieldset></div>
  7. small, disabled options and the legend as page heading

    <CheckboxGroup
      id="f"
      legend="Filter"
      isPageHeading
      small
      options={[{"value":"open","label":"Open"},{"value":"closed","label":"Closed","disabled":true}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend fune-legend--heading"><h1 class="fune-legend-heading">Filter</h1></legend><div class="fune-checkboxes fune-checkboxes--small"><div class="fune-checkbox"><input class="fune-checkbox-input" id="f" type="checkbox" name="f" value="open"/><label class="fune-label fune-checkbox-label" for="f">Open</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="f-2" type="checkbox" disabled="" name="f" value="closed"/><label class="fune-label fune-checkbox-label" for="f-2">Closed</label></div></div></fieldset></div>
  8. disabled disables every box; a class name joins the wrapper's

    <CheckboxGroup
      id="d"
      legend="Days"
      disabled
      className="wide"
      options={[{"value":"mon","label":"Monday"},{"value":"tue","label":"Tuesday","disabled":false}]}
    />

    renders

    <div class="fune-field wide"><fieldset class="fune-fieldset"><legend class="fune-legend">Days</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="d" type="checkbox" disabled="" name="d" value="mon"/><label class="fune-label fune-checkbox-label" for="d">Monday</label></div><div class="fune-checkbox"><input class="fune-checkbox-input" id="d-2" type="checkbox" disabled="" name="d" value="tue"/><label class="fune-label fune-checkbox-label" for="d-2">Tuesday</label></div></div></fieldset></div>
  9. labels and values are escaped

    <CheckboxGroup
      id="e"
      legend="Q & A"
      options={[{"value":"a&b","label":"<A> & \"B\""}]}
    />

    renders

    <div class="fune-field"><fieldset class="fune-fieldset"><legend class="fune-legend">Q &amp; A</legend><div class="fune-checkboxes"><div class="fune-checkbox"><input class="fune-checkbox-input" id="e" type="checkbox" name="e" value="a&amp;b"/><label class="fune-label fune-checkbox-label" for="e">&lt;A&gt; &amp; &quot;B&quot;</label></div></div></fieldset></div>
  10. no options is refused

    <CheckboxGroup id="x" legend="Pick" options={[]} />

    error: a checkbox group needs at least one option

Show the other 2 tests
  1. two options with one value are refused

    <CheckboxGroup
      id="x"
      legend="Pick"
      options={[{"value":"a","label":"A"},{"value":"a","label":"Also A"}]}
    />

    error: checkbox option values must be unique: "a" is there twice

  2. an id with a space is refused

    <CheckboxGroup
      id="my waste"
      legend="Waste"
      options={[{"value":"a","label":"A"}]}
    />

    error: a field id cannot contain spaces

More from the author

- **onChange** is called with every ticked value after each change, not with the event, and always in the options' order, whatever order they were ticked in. With **values** it is a controlled group; with **defaultValues** (or neither) the component keeps the list itself and still reports it. - **onBlur** is called, with nothing, when any box in the group loses focus, so a form can mark the question touched (`form.state`'s blur action). - **toggleValue(options, values, value, checked)** is that update. The result holds only the options' values, once each, in their order, so a stale value from an old option list drops out. Ticking an `exclusive` option clears the others and ticking another clears it, which is what GOV.UK's `data-behaviour="exclusive"` does in the browser; the input carries that attribute too. A value that is not an option is refused. - **Ids.** The first checkbox's id is the `id`, the rest `<id>-2`, `<id>-3`... as GOV.UK does, so an error summary linking to `#<id>` lands on the first box. Every box posts under **name** (the id when left out). An item hint is `<item id>-item-hint` and describes only its box. - **The group's hint and error** are `<id>-hint` and `<id>-error`, as on GOV.UK; the `<fieldset>` itself has no id (`react.form.fieldset` uses the id only to name them), so the first checkbox can have it. - **The error is the group's.** As on GOV.UK, the hint and error message describe the fieldset (announced once on entering the group), and no checkbox is marked `aria-invalid`: the mistake is in the answer as a whole. - **small** gives `fune-checkboxes--small`, for filters and dense pages. **disabled** disables every box; an option's own `disabled` disables it. - Option values must be unique (they are the React keys), and a group needs at least one option.

For one checkbox on its own ("I agree"), use `react.form.checkbox`. It is a client component (state and change listeners), so the built file starts with `"use client"`.

Classes: those of `react.form.fieldset`, plus `fune-checkboxes` (and `fune-checkboxes--small`), `fune-checkbox` (one box and its label), `fune-checkbox-input`, `fune-label fune-checkbox-label`, `fune-hint fune-checkbox-hint`, `fune-checkboxes-divider`.

Sources: GOV.UK Design System, "Checkboxes" (item ids, item hints, the "or" divider and the exclusive "none" option, small checkboxes, errors on the fieldset) https://design-system.service.gov.uk/components/checkboxes/ and "Fieldset" https://design-system.service.gov.uk/components/fieldset/.

## 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

PathBytes
NOTICE1,223
README.md3,836
impl/typescript/checkbox_group.tsx3,400
impl/typescript/toggle_value.ts1,333
vectors.json12,160