Checkbox Card Group
A stacked list of independent on/off options, each with an icon, a title, and an explanation.
When to Use
Variants
Default
One row per option. Pre-check the ones that are safe by default and leave the consequential ones off — here, reordering the agenda is opt-in.
Cleanup options
<?= ws_checkbox_card_group([
['id' => 'roundDurations',
'title' => 'Round durations',
'description' => 'Round all times to the nearest 5 minutes',
'icon' => 'schedule',
'iconColor' => 'blue',
'checked' => true],
['id' => 'applyStructure',
'title' => 'Apply recommended structure',
'description' => 'Reorder for optimal flow',
'icon' => 'auto_fix_high',
'iconColor' => 'red',
'highlight' => true],
], ['label' => 'Cleanup options']) ?>Icon colours
Five accent keys: blue, orange, purple, red, green. These are functional coding — use one colour per category of operation and hold it, rather than cycling for variety.
Accent keys
States
| State | Behavior |
|---|---|
| Unchecked | Default row treatment, driven by the input's native state. |
| Checked | Set by checked on the item and maintained by the native :checked pseudo-class — no JS keeps it in sync. |
| Highlight | highlight: true gives a row a featured treatment. Use it for the option with the largest consequence, not for the recommended one. |
| Hover | Row background shifts; the whole row is the click target because the label wraps it. |
| Focus | The real checkbox takes focus and shows its ring on the row. |
| Disabled | Not supported. Unlike Radio Card Group, items have no disabled option. Omit an unavailable option, or explain in its description why it can't be used. |
Real-World Usage
The Planner's agenda cleanup modal: the safe operations start checked, the one that reorders the user's work does not, and it carries highlight so it reads as the significant choice.
What should we clean up?
<?= ws_checkbox_card_group($operations, ['label' => 'What should we clean up?']) ?>
<script>
// Item `id` becomes the checkbox id — read them back by that.
const selected = $operations
.map(op => op.id)
.filter(id => document.getElementById(id).checked);
</script>Options
Group options
| Option | Type | Default | Purpose |
|---|---|---|---|
$items | array | [] | Positional. The option definitions. |
label | string | null | Group label. Not programmatically associated — see Accessibility. |
id | string | null | Container ID |
class | string | '' | Additional CSS classes |
Item options
| Option | Type | Default | Purpose |
|---|---|---|---|
id | string | '' | Required in practice — it becomes the checkbox's id and the handle you read the value back by. Each must be unique on the page. |
title | string | '' | Row title. Phrase it as the action: "Round durations". |
description | string | '' | What checking it will do |
icon | string | null | Material icon name |
iconColor | string | 'blue' | blue | orange | purple | red | green |
checked | bool | false | Initially checked |
highlight | bool | false | Featured row treatment |
name, so the checkboxes aren't submitted as an array. Read them back individually by their id, as in the example above.
Accessibility
| Concern | Behavior |
|---|---|
| Semantics | Real <input type="checkbox"> elements, so checked state, focusability, and keyboard operation are all native. |
| Keyboard | Tab between rows, Space to toggle. Each checkbox is its own tab stop, which is correct for independent options. |
| Labelling per option | The row is a <label> wrapping its input, so title and description are both part of the accessible name and the whole row is clickable. |
| Group label | Not associated. Like Radio Card Group, the label renders as a plain element with no <fieldset>/<legend> and no role="group" with aria-labelledby. Wrap the call yourself when the question matters. |
| Icon colour | Decorative. The five accents carry no meaning to assistive tech, so never let colour be the only thing distinguishing two rows — the title and description must stand alone. |
| Highlight | Visual only, with no text equivalent. If a row is highlighted because it's consequential, say so in the description. |
<fieldset>
<legend>What should we clean up?</legend>
<?= ws_checkbox_card_group($operations) ?>
</fieldset>Tokens
| Token | Used for |
|---|---|
--color-ink-secondary | Group label colour |
--text-small | Group label size |
The rows lean on the shared card and toggle rules for surface, border, and the five icon accents rather than defining tokens of their own.
CSS Classes
| Class | Purpose |
|---|---|
.ws-checkbox-card-group | Stacked container |
.ws-checkbox-card-group__label | Group label (unassociated — see Accessibility) |
Row-level styling comes from the shared checkbox-card rules rather than a per-row modifier — which is why the class list here is short.
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_checkbox_card_group() helper function |
includes/components/checkbox-card-group.php | Template — row markup and icon accent mapping |
includes/components/components.css | Styles (.ws-checkbox-card-group rules) |