Checkbox - headless
Styled SvCheckBox plus a custom card checkbox, one bound checked value with a readout.
A live, editable Svelte 5 component example from the SvGrid gallery (Headless Editors). Read the SvGrid UI component docs for the full API.
About this example
The headless checkbox core of the SvGrid UI kit in Svelte 5. createCheckbox is the state machine behind SvCheckBox: checked, unchecked and indeterminate with aria-checked set to mixed, exposed as boxProps to spread onto any element. The same core drives the styled component and a custom card-style checkbox, both bound to one checked value.
Headless checkbox - the SvGrid UI kit is headless-first + render-ready, just like the grid. createCheckbox is the state machine behind <SvCheckBox>: checked / unchecked / indeterminate + ARIA (aria-checked="mixed"), exposed as **prop-getters** you spread onto YOUR OWN markup. Below, the same core drives the styled component AND a totally custom render, bound to one value.
Imports, features and API used
Imports: @svgrid/grid
Frequently asked questions
How is indeterminate represented?
As a separate flag the core exposes; boxProps sets aria-checked to mixed while it is on and the next click resolves to checked, matching native behaviour.
Can the whole card be the click target?
Yes. Spread boxProps on the card element and the entire surface toggles the value with the right role and keyboard handling.
Does it work inside a label?
Yes. The element with boxProps carries an id you can point a label at, or wrap it in a label as the styled component does.
Related documentation
Related articles
- svelte-headless-table and the Svelte 5 upgrade: your three options - svelte-headless-table has not shipped since October 2024 and declares svelte@^4. Here are the three real paths off it - the maintained fork, TanStack Table v9, or a rendered grid - and how to tell which one is yours.
- What Is a Headless Data Grid? - Headless grids separate behavior from rendering - sorting, filtering, grouping, and pagination logic with no markup attached. Here is what that means in practice, why it matters, and when the tradeoff is worth it.
- Inside SvGrid: Server-Side Data and the Headless Core - How SvGrid separates UI state from row processing, and what that means for building grids driven entirely by a backend.
Source code (273-headless-checkbox.svelte)
<script lang="ts">
/**
* Headless checkbox - the SvGrid UI kit is headless-first + render-ready, just
* like the grid. `createCheckbox` is the state machine behind <SvCheckBox>:
* checked / unchecked / indeterminate + ARIA (`aria-checked="mixed"`), exposed
* as **prop-getters** you spread onto YOUR OWN markup. Below, the same core
* drives the styled component AND a totally custom render, bound to one value.
*/
import { SvCheckBox, createCheckbox } from '@svgrid/grid'
let value = $state(false)
// The headless core - identical behavior, our own DOM.
const cb = createCheckbox({
checked: () => value,
onChange: (v) => (value = v),
ariaLabel: () => 'Accept terms',
})
</script>
<div class="wrap">
<header>
<h2>Headless checkbox</h2>
<p>
Every editor ships a headless core + a styled component - the same split as the grid
(<code>createSvGrid</code> / <code><SvGrid></code>). Here <code>createCheckbox</code>
drives both renders below; the toggle logic and ARIA come from the core, the markup is yours.
</p>
</header>
<div class="cols">
<section>
<h3>Styled <code><SvCheckBox></code></h3>
<SvCheckBox checked={value} onChange={(v) => (value = v)}>Accept terms</SvCheckBox>
</section>
<section>
<h3>Your markup, same core</h3>
<button class="card" class:on={cb.checked} {...cb.boxProps()}>
<span class="mark">{cb.checked ? '✓' : ''}</span>
<span class="txt">Accept terms</span>
</button>
<p class="hint">Tab in, then Space/Enter toggles - all from <code>createCheckbox</code>.</p>
</section>
</div>
<aside class="readout">
<h3>Checked</h3>
<div class="tags"><span class="tag">{value}</span></div>
</aside>
</div>
<style>
.wrap { padding: 20px; max-width: 900px; display: flex; flex-direction: column; gap: 18px; }
header h2 { margin: 0 0 4px; font-size: 20px; font-weight: 700; }
header p { margin: 0; color: var(--sg-muted, #64748b); font-size: 13.5px; line-height: 1.55; max-width: 680px; }
code { background: var(--sg-row-hover-bg, #eef2ff); padding: 1px 5px; border-radius: 5px; font-size: 12px; }
.cols { display: flex; gap: 32px; flex-wrap: wrap; align-items: flex-start; }
section h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
/* Fully custom render - nothing shared with SvCheckBox but the core. */
.card {
display: inline-flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 10px;
font: inherit; font-size: 13px; font-weight: 550; cursor: pointer;
border: 1px solid var(--sg-border, #e2e8f0); background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
transition: border-color 0.12s, background 0.12s;
}
.card .mark {
width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center; font-size: 13px;
border: 1.5px solid var(--sg-border, #cbd5e1); color: var(--sg-on-accent, #fff); transition: background 0.12s, border-color 0.12s;
}
.card.on { border-color: var(--sg-accent, #4f46e5); }
.card.on .mark { background: var(--sg-accent, #4f46e5); border-color: transparent; }
.card:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #4f46e5)); outline-offset: 2px; }
.hint { margin: 10px 0 0; font-size: 12px; color: var(--sg-muted, #94a3b8); line-height: 1.5; }
.readout h3 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
.tags { display: flex; gap: 6px; flex-wrap: wrap; }
.tag { padding: 3px 10px; border-radius: 6px; font-size: 13px; background: var(--sg-row-hover-bg, #f1f5f9); }
</style>More Headless Editors examples
- Headless editors - Headless-first, like the grid: createListbox is the state machine behind SvListBox (roving focus, single/multi selection, keyboard, ARIA) exposed as prop-getters you spread onto YOUR own markup. One core drives both the styled SvListBox and a custom chip-cloud render, bound to one value.
- Combobox - headless - createCombobox drives the styled SvComboBox and a custom filter-input render, both bound to one value with a readout.
- Dropdown list - headless - createDropdownList drives SvDropDownList and a custom trigger+menu render, sharing one value.
- Autocomplete - headless - createAutocomplete drives SvAutoComplete and a custom suggestion-list render over one text value.
- Tags input - headless - createTagsInput drives SvTagsInput and a custom chip-cloud render, both editing one string array.