> For the complete documentation index, see [llms.txt](https://sygnal.gitbook.io/sygnal-webflow-components/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sygnal.gitbook.io/sygnal-webflow-components/cc/sygnal-forms/toggle-checkbox/checkbox-technical-notes.md).

# Checkbox Technical Notes

## Submission Basics

* A checked checkbox submits `name=VALUE`. If you don’t set `value`, browsers send `on`.
* An unchecked checkbox submits **nothing**.

## Change the submitted values

### True value

Pick your “true” value with the `value` attribute:

```html
<input type="checkbox" name="agree" value="1">
```

### False value&#x20;

To also send a “false” value when unchecked, use one of these:

1. Hidden fallback (no JS; two controls with same name)

```html
<input type="hidden"  name="agree" value="0">
<label><input type="checkbox" name="agree" value="1"> I agree</label>
```

Server receives `agree=0` when unchecked, `agree=0&agree=1` when checked (order = DOM order). If your backend can’t handle duplicate keys, disable the hidden input when checked:

```html
<script>
const cb = document.querySelector('input[type=checkbox][name=agree]');
const hid = document.querySelector('input[type=hidden][name=agree]');
cb.addEventListener('change', () => { hid.disabled = cb.checked; });
</script>
```

2. `formdata` hook (single definitive value)

```html
<form id="f">
  <label><input id="agree" type="checkbox" name="agree" value="1"> I agree</label>
</form>
<script>
const f = document.getElementById('f');
const cb = document.getElementById('agree') as HTMLInputElement;
f.addEventListener('formdata', e => {
  e.formData.set('agree', cb.checked ? '1' : '0'); // always one key
});
</script>
```

3. Handle on the server: treat missing key as false.

## A11y checklist

* Provide a real `<label>` (wrap or `for`). This is the accessible name and expands the click target.
* Keep it a native `<input type="checkbox">`; don’t replace with divs. If styled as a toggle, keep checkbox semantics (no extra `role`).
* Don’t rely on color alone for state; ensure a positional/icon change and visible focus outline.
* Group related boxes with `<fieldset><legend>…</legend></fieldset>`.
* Use `required`, `aria-invalid`, and an inline error message (referenced with `aria-describedby`) when applicable.
* For tri-state use `el.indeterminate = true` (JS) if needed.

### Using \<legend>&#x20;

`<fieldset><legend>` grouping

* Purpose: programmatically group related controls (e.g., a set of checkboxes answering one question). Screen readers announce the `legend` as the group label when navigating within the group.
* Rules:
  * Put one `<legend>` as the **first child** of `<fieldset>`.
  * Keep a meaningful `legend` that reads well before each inner control’s label.
  * Use when instructions/errors apply to the whole group.

Example with error/help

```html
<fieldset aria-describedby="pets-hint pets-err">
  <legend>Select your pets</legend>
  <p id="pets-hint">Choose all that apply.</p>

  <label><input type="checkbox" name="pets" value="dog"> Dog</label>
  <label><input type="checkbox" name="pets" value="cat"> Cat</label>
  <label><input type="checkbox" name="pets" value="bird"> Bird</label>

  <p id="pets-err" class="visually-hidden">You must select at least one.</p>
</fieldset>
```

Notes

* Do **not** replace native checkboxes with `div` + ARIA unless necessary. If you must, use `role="checkbox"`, manage `tabindex`, Space toggling, and `aria-checked="true|false|mixed"`.
* Form reset clears `indeterminate` to `false`; reapply in your code if a mixed state should remain after reset.

## Indeterminate State&#x20;

Use cases???

{% hint style="warning" %}
**CMS DATA-BINDING** \
Not suitable for CMS Switch field binding. Would need to use a text field likely.&#x20;
{% endhint %}

* Supported: yes, via the `HTMLInputElement.indeterminate` **property** (JS-only). There is **no** `indeterminate` HTML attribute.
* Semantics: a visual “mixed/partial” state. It does **not** affect form submission; only `checked` submits.
* Interaction: on user click/Space, browsers automatically set `indeterminate = false` and then toggle `checked`. Re-set it in code if needed.
* Styling: use the `:indeterminate` CSS pseudo-class.
* A11y: announced as “mixed” by modern AT; keep a clear label and optional help text.

```html
<label><input id="parent" type="checkbox"> Select all</label>
<div>
  <label><input class="child" type="checkbox"> A</label>
  <label><input class="child" type="checkbox"> B</label>
  <label><input class="child" type="checkbox"> C</label>
</div>

<script>
// parent controls children
const parent = document.getElementById('parent') as HTMLInputElement;
const kids = Array.from(document.querySelectorAll<HTMLInputElement>('.child'));

function syncParent() {
  const checked = kids.filter(k => k.checked).length;
  parent.checked = checked === kids.length && checked > 0;
  parent.indeterminate = checked > 0 && checked < kids.length;
}

parent.addEventListener('input', () => {
  kids.forEach(k => k.checked = parent.checked);
  parent.indeterminate = false;
});

kids.forEach(k => k.addEventListener('input', syncParent));
syncParent();
</script>

<style>
/* optional visual for mixed state */
input[type="checkbox"]:indeterminate { outline: 2px solid #f59e0b; }
</style>

```

Submitting a tri-state

* Hidden fallback:

```html
<input type="hidden" name="opt" value="none">
<input id="opt" type="checkbox" name="opt" value="all">
<script>
  const cb = document.getElementById('opt') as HTMLInputElement;
  // If you use a mixed state, set a separate hidden input to 'mixed'
</script>
```

* Or use the `formdata` event to set exactly one value (`none`/`mixed`/`all`).

```js
form.addEventListener('formdata', e => {
  let v = 'none';
  if (parent.indeterminate) v = 'mixed';
  else if (parent.checked) v = 'all';
  e.formData.set('opt', v);
});
```
