Checkbox
A native-feeling checkbox built on a role="checkbox" element. It generates a hidden native <input type="checkbox"> for form submission, supports indeterminate state, and emits change events on user interaction.
import { Checkbox } from "@areia/slots";
document.querySelector("#root")!.innerHTML = `
<label>
<span
data-slot="checkbox"
class="win95-check mr-2 data-[checked]:bg-blue-900"
>
<span data-slot="checkbox-indicator" class="win95-check-mark"></span>
</span>
Accept terms and conditions
</label>
`;
const root = document.querySelector('[data-slot="checkbox"]')!;
const controller = Checkbox.createCheckbox(root, {
defaultChecked: false,
name: "terms",
});
Import
import { Checkbox } from "@areia/slots";
Usage
import { Checkbox } from "@areia/slots";
// Auto-bind a single root
const root = document.querySelector('[data-slot="checkbox"]');
const checkbox = Checkbox.createCheckbox(root, {
name: "accept",
value: "yes",
});
// Listen for state changes
root.addEventListener("checkbox:change", (e) => {
console.log(e.detail.checked);
});
// Auto-bind all unbound roots in scope
const controllers = Checkbox.create();
Expected Markup
The root element receives role="checkbox" and keyboard handling. A hidden native <input type="checkbox"> is generated and inserted after the root for form participation. The optional checkbox-indicator slot is shown or hidden based on checked/indeterminate state.
<label>
<span data-slot="checkbox">
<span data-slot="checkbox-indicator">
<!-- Icon or custom indicator markup -->
</span>
</span>
Accept terms and conditions
</label>
Wrapping the checkbox in a <label> is recommended — the controller detects wrapping labels and label[for] associations so that clicking the label text toggles the checkbox.
Minimal Markup (no indicator)
<span data-slot="checkbox"></span>
Generated Hidden Input
The controller creates a visually hidden native checkbox and inserts it immediately after the root element:
<input
type="checkbox"
tabindex="-1"
aria-hidden="true"
data-checkbox-generated="input"
style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;pointer-events:none"
/>
When uncheckedValue is configured, a second hidden input is generated to submit a value even when the checkbox is unchecked.
Data Slots
| Slot | Required | Description |
|---|---|---|
checkbox | Yes | Root element. Receives role="checkbox" and ARIA attrs. |
checkbox-indicator | No | Visual indicator shown when checked or indeterminate. Hidden otherwise unless data-keep-mounted is set. |
Generated Attributes
| Attribute | Element(s) | Description |
|---|---|---|
data-checked | Root, indicator | Present when checked. |
data-unchecked | Root, indicator | Present when unchecked and not indeterminate. |
data-indeterminate | Root, indicator | Present when in mixed state. |
data-disabled | Root, indicator | Present when disabled. |
data-readonly | Root, indicator | Present when read-only. |
data-required | Root, indicator | Present when required. |
role | Root | Set to "checkbox". |
aria-checked | Root | "true", "false", or "mixed". |
aria-disabled | Root | "true" or removed. |
aria-readonly | Root | "true" or removed. |
aria-required | Root | "true" or removed. |
aria-labelledby | Root | Merged from wrapping <label> and [for] associations. |
Events
Outbound
| Event | Detail | Description |
|---|---|---|
checkbox:change | { checked: boolean } | Fired on the root when the checked state changes via user interaction or the controller. |
Inbound
| Event | Detail | Description |
|---|---|---|
checkbox:set | boolean or { checked: boolean } or { checked: boolean; indeterminate?: boolean } | Dispatch on the root to set the checked (and optionally indeterminate) state. Supports flat boolean shorthand. |
API Reference
Options
| Option | Type | Default | Description |
|---|---|---|---|
defaultChecked | boolean | false | Initial checked state. |
indeterminate | boolean | false | Initial mixed state. Cleared when the user toggles the checkbox. |
disabled | boolean | false | Disable user interaction and exclude from form submission. |
readOnly | boolean | false | Prevent user interaction while keeping the value submittable. |
required | boolean | false | Require a checked value for native form validation. |
name | string | — | Form field name. |
form | string | — | Form owner ID for generated inputs. |
value | string | — | Submitted value when checked. Defaults to the native checkbox "on". |
uncheckedValue | string | — | Submitted value when unchecked. Creates an additional hidden input. |
onCheckedChange | (checked: boolean) => void | — | Called when the checked state changes. |
Controller
| Member | Type | Description |
|---|---|---|
checked | boolean (getter) | Current checked state. |
indeterminate | boolean (getter) | Current indeterminate state. |
toggle() | () => void | Toggle the checked state and clear indeterminate. |
check() | () => void | Set checked to true and clear indeterminate. |
uncheck() | () => void | Set checked to false and clear indeterminate. |
setChecked(checked, ind?) | (checked: boolean, indeterminate?: boolean) => void | Set checked and optionally indeterminate state. |
setIndeterminate(ind) | (indeterminate: boolean) => void | Set indeterminate without changing checked. |
destroy() | () => void | Remove listeners and generated inputs. |
Controller
The controller is returned by Checkbox.createCheckbox() and provides imperative control over the checkbox state.
const checkbox = Checkbox.createCheckbox(root, {
name: "terms",
});
// Toggle
checkbox.toggle();
// Force check
checkbox.check();
// Force uncheck
checkbox.uncheck();
// Set checked and indeterminate together
checkbox.setChecked(true, true); // indeterminate
// Set only indeterminate
checkbox.setIndeterminate(true);
// Read state
console.log(checkbox.checked); // true
console.log(checkbox.indeterminate); // true
// Clean up
checkbox.destroy();
Form Integration
The generated hidden input participates in standard form submission and FormData. When uncheckedValue is set, an extra hidden input ensures a value is submitted even when the checkbox is unchecked. The controller also listens for the form's reset event and synchronizes state accordingly.
<form>
<span
data-slot="checkbox"
data-name="accept"
data-value="yes"
data-unchecked-value="no"
>
<span data-slot="checkbox-indicator"></span>
</span>
Accept terms
</form>