Task Checklist
Tracks activation tasks against real data, with each undone item linking to the place it gets done.
When to Use
Variants
Full — progress, phases, descriptions
The complete form. Descriptions explain the value of each step and disappear once it's done, so the list gets quieter as the user progresses.
Getting started
Four steps to your first workshop that works.
-
Plan Build your first agenda (done) Drag a few activities onto the timeline — it takes about five minutes.
-
Plan Save it so it's yours anywhere (done) A saved agenda opens on any device and can be shared with your team.
- Reflect Take the 3-minute DNA quiz Find out which of the five facilitator archetypes matches how you run a room.
- Learn Find an exercise you'd actually run Browse the library and favourite one for your next session.
<?= ws_task_checklist([
['label' => 'Build your first agenda',
'description' => 'Drag a few activities onto the timeline.',
'href' => '/planner/',
'done' => $user->hasPlan(),
'phase' => 'Plan',
'key' => 'build'],
], [
'title' => 'Getting started',
'subtitle' => 'Four steps to your first workshop that works.',
'dismissible' => true,
]) ?>Complete — the celebration card
Every item done, and the card says so: a red-tinted surface, red filled checks, and labels back to full ink. These lines are the achievement being named, not chores being crossed off. Pass past-tense labels, dismissible, and a cta that points forward. Decide deliberately what happens after dismissal — a finished checklist that never goes away becomes clutter. The app shell's rail journey card (app/includes/rail.php) is the reference consumer.
That's one all the way round.
Designed, delivered, synthesized. From here the dashboard reads from your own workshops.
-
Plan Built an agenda (done)
-
Facilitate Ran the session (done)
-
Synthesize Made sense of what the room produced (done)
<?= ws_task_checklist($doneSteps, [
'title' => "That's one all the way round.",
'subtitle' => 'Designed, delivered, synthesized. From here the dashboard reads from your own workshops.',
'dismissible' => true,
'cta' => ['label' => 'Plan the next one', 'href' => '/app/?view=plan&step=home'],
]) ?>Counted — a row that names its queue
A row can carry a badge: a short count the label needs to say out loud, rendered as a quiet pill after it. Use it where the step is about things the user has waiting, so the row and the number agree, and point the row's href at the first of them rather than at a list to hunt through. A done row with a badge keeps its link and arrow — the step has been taken once and more is queued behind it.
Getting started
-
Build an agenda (done)
- Run the session (done) 2 ready
- Synthesize a workshop 3 waiting
<?= ws_task_checklist([
['label' => 'Synthesize a workshop',
'done' => false,
'href' => '/app/?view=synthesize&step=make&plan=' . $firstWaitingId,
'badge' => $waiting . ' waiting',
'key' => 'synthesize'],
], ['title' => 'Getting started', 'progress' => false]) ?>Minimal
No progress bar, no phases, no descriptions — for a short list where the count would be noise.
Set up your team
<?= ws_task_checklist($items, ['title' => 'Set up your team', 'progress' => false]) ?>
States
| State | Behavior |
|---|---|
| Undone | Renders as a link to href with a hollow icon, its description, and a trailing arrow. This is the actionable state. |
| Done | Filled check in --color-success, description hidden, and no longer a link — there's nothing left to do there. |
| Complete | All items done adds .ws-task-checklist--complete: red-tinted surface, red checks, labels back to full ink. Pair with dismissible and a forward-pointing cta. |
| CTA | cta renders one ws_button inside the card, below the items — the card travels as one object into a rail or a panel. |
Undone with no href | Renders as plain text. Valid, but the user is then told what to do without being shown where — supply an href. |
| Progress | On by default: an "x of N done" count and a bar, both computed from the items' done flags. No separate total to keep in sync. |
| Dismissed | dismissible renders a button carrying data-checklist-dismiss. The component doesn't handle the click — wire it and persist the choice, or the checklist returns on the next load. |
| Hover | Undone rows lift their background; done rows are inert. |
| Focus | Undone rows are links with a --focus-ring outline. |
Real-World Usage
The MyWorkshopr activation panel. Every done flag is a query against real state — that's the contract, and it's what stops the list lying to the user.
<?php
$hasPlan = (bool) getPlanCount($userId);
$hasSaved = (bool) getSavedPlanCount($userId);
$hasDna = (bool) getDnaResult($userId);
$hasFav = (bool) getFavouriteCount($userId);
$tasks = [
['label' => 'Build your first agenda', 'href' => '/planner/', 'done' => $hasPlan,
'phase' => 'Plan', 'key' => 'build',
'description' => 'Drag a few activities onto the timeline.'],
['label' => 'Save it so it\'s yours anywhere', 'href' => '/planner/', 'done' => $hasSaved,
'phase' => 'Plan', 'key' => 'save'],
['label' => 'Take the 3-minute DNA quiz', 'href' => '/dna/', 'done' => $hasDna,
'phase' => 'Reflect', 'key' => 'dna'],
['label' => 'Find an exercise you\'d actually run', 'href' => '/library/', 'done' => $hasFav,
'phase' => 'Learn', 'key' => 'library'],
];
// Hide it entirely once everything is done, rather than showing a finished list.
if (in_array(false, array_column($tasks, 'done'), true)):
?>
<?= ws_task_checklist($tasks, ['title' => 'Getting started', 'dismissible' => true]) ?>
<?php endif; ?>
<script>
document.querySelector('[data-checklist-dismiss]')?.addEventListener('click', async () => {
await fetch('/api/prefs.php?action=dismiss_checklist', { method: 'POST' });
document.querySelector('.ws-task-checklist').remove();
});
</script>Options
Checklist options
| Option | Type | Default | Purpose |
|---|---|---|---|
$items | array | [] | Positional. The task definitions, in the order they should be done. |
title | string | 'Getting started' | Heading, set in Fraunces |
subtitle | string | null | Supporting line under the title |
progress | bool | true | Show the "x of N done" count and bar |
dismissible | bool | false | Render the dismiss button. Your JS handles the click. |
cta | array | null | In-card action below the items, via ws_button: label (required), href, variant (default primary), icon (default arrow_forward). |
id | string | null | Element ID |
class | string | '' | Additional CSS classes |
Item options
| Option | Type | Default | Purpose |
|---|---|---|---|
label | string | required | The task. Phrase it as the outcome the user gets. |
done | bool | false | Completion state. Compute it from real data. |
href | string | null | Where the task happens. Undone items with an href render as links. |
description | string | null | Why it's worth doing. Hidden once done. |
phase | string | null | Uppercase eyebrow — Learn, Plan, Facilitate, Reflect |
key | string | null | Emitted as data-task for JS and analytics |
Accessibility
| Concern | Behavior |
|---|---|
| State icons | The done and todo icons carry aria-hidden="true", so the tick isn't read as stray text. |
| Dismiss button | Labelled aria-label="Hide this checklist" — it says what it does rather than just "close". |
| Keyboard | Undone items are real links in the tab order; done items aren't focusable, which correctly signals there's nothing to do. |
| Focus | Links use --focus-ring with --focus-ring-offset, matching the rest of the platform. |
| Completion state | Worth knowing: done versus undone is conveyed by icon and colour, with no text equivalent on the row. The progress count ("2 of 4 done") carries it for the list as a whole, but an individual row's state isn't announced. If a checklist ships without the progress bar, consider adding visually-hidden text to each row. |
| Link text | The label is the link's accessible name, so it must stand alone in a screen reader's link list — "Build your first agenda" does; "Get started" wouldn't. |
| Contrast | Done uses --color-success, phase eyebrows --phase-learn-dark, descriptions --text-muted — all tested. |
Tokens
| Token | Used for |
|---|---|
--bg-surface / --bg-subtle | Panel background and row hover |
--color-success | Completed check |
--mainsite-primary | Progress bar fill |
--phase-learn-dark | Phase eyebrow |
--gray-100 / --gray-200 / --gray-300 | Dividers, progress track, and the todo icon |
--text-dark / --text-muted / --text-faint | Title, description, and completed-label text |
--focus-ring / --focus-ring-offset | Link focus treatment |
--radius-lg / --radius-2xl / --radius-full | Panel, row, and progress-bar corners |
--text-h3 / --text-body / --text-small / --text-meta / --text-caption | Type scale across title, label, and eyebrow |
--font-heading / --font-body / --font-medium / --font-semibold | Fraunces title, Inter body |
--tracking-wide | Phase eyebrow letter-spacing |
--space-1 through --space-6 | Internal rhythm |
--transition-fast | Row hover |
CSS Classes
| Class | Purpose |
|---|---|
.ws-task-checklist | Panel container |
.ws-task-checklist__header | Title, subtitle, and dismiss row |
.ws-task-checklist__title / __subtitle | Heading text |
.ws-task-checklist__dismiss | Dismiss button, carrying data-checklist-dismiss |
.ws-task-checklist__progress / __progress-bar / __progress-count | Progress display |
.ws-task-checklist__body / __items | List wrappers |
.ws-task-checklist__item / __row | An individual task and its layout row |
.ws-task-checklist__check | State icon holder |
.ws-task-checklist__icon-done / __icon-todo | The two state icons |
.ws-task-checklist__phase | Uppercase phase eyebrow |
.ws-task-checklist__label / __desc | Task text |
.ws-task-checklist__arrow | Trailing arrow on actionable rows |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_task_checklist() helper function |
includes/components/task-checklist.php | Template — progress calculation and row markup |
includes/components/components.css | Styles (.ws-task-checklist rules) |
No JS file — dismissal is left to the consumer, as noted in States.