Task Checklists
Interactive, persistent task checklists for sequential how-to steps. This is the design contract for the checklist element of the article body; authoring syntax lives in Article Body Elements.
What It Is
On single documentation posts, markdown - [ ] Task / - [x] Task bullets are converted by js/article-content.js into clickable, Notion-style checkboxes at render time. Checked state persists per post, per visitor in localStorage, so progress survives reloads. Each checklist group (<ul>) with two or more items shows a live "X of Y done" progress badge + bar above it, a per-group Reset button, and a completion checkmark when every item is checked.
Progress headers are intentionally not rendered for single-item lists — no noisy 1 of 1 done. Instead a single-item checklist shows a subtle "Checklist" hint pill so it is still identifiable as an interactive checklist. The left alignment and checkbox conversion still apply.
Score-90 features (theme v1.20)
| Feature | What it does |
|---|---|
| Per-group Reset | Each progress header carries its own Reset button that clears only that group and re-saves localStorage. |
| Completion checkmark | When a group is fully checked, a green ✓ badge scales in beside the "X of Y done" count and the bar flips green. |
| Single-item hint | A 1-item checklist shows a Checklist pill instead of a progress header, so it is not mistaken for a plain bullet. |
| Global Reset | The Reset checklist button at the end of the article still clears all groups at once. |
The score-90 upgrade is demonstrated live on demo/checklist-ux-demo.md (https://id86.net/demo/checklist-ux-demo/).
Visual Contract
Row card
Each task row is a card: light fill, thin border, a 3px left accent rail, soft shadow, and rounded corners. Checkbox always leads; title and checkbox are inline; optional muted description sits under the title, indented to align with the text (not the checkbox).
| Property | Value |
|---|---|
| Idle background | #fcfeff (almost white) |
| Border | 1px #dbe8f2 |
| Left accent rail | 3px #dbe8f2 (idle) |
| Radius | 10px |
| Shadow | 0 2px 8px rgba(11, 33, 73, .04) |
| Margin / padding | 0 0 .55rem / .72rem .9rem (mobile .68rem .72rem) |
| Checkbox | 19px, 2px #b9c5d2 border, 5px radius, white fill |
State colors
| State | Background | Left rail | Notes |
|---|---|---|---|
| Idle | #fcfeff | #dbe8f2 | default |
| Hover | #f4faff | #2597d5 | border #b9d8ec, lifted shadow 0 5px 14px rgba(11,33,73,.08), translateY(-1px) |
Checked (is-done) | #e8f3fb | #2273bf | border #c8e2f3; text #7c8aa0 + line-through |
| Checked + hover | #dceefa | #2597d5 | via .is-done:hover |
Checked marker: blue border #2273bf, brand-tint fill #e8f3fb, and an inline SVG checkmark (stroke #2273bf).
Progress header
| Property | Value |
|---|---|
| Count | Pill badge: bg #eef6fb, color #2273bf, .75rem 600, radius 999px, padding .25rem .55rem |
| Bar | flex: 1 1 auto, 7px, radius 999px, track #e2e9f1 |
| Fill | #2273bf, width transition .25s |
Complete (is-complete) | Count + fill turn green #2f9e44 |
| Checkmark | 20px circle, bg #e8f3fb, color #2273bf, .8rem 700, hidden by default, scales in on .is-visible |
| Group reset | Bordered pill Reset: #fff fill, 1px #cbddea border, #2273bf text, radius 6px, .2rem .5rem padding, .72rem font |
Container is a flex row with gap .65rem, margin 1.25rem 0 .65rem, capped at max-width: 420px.
Single-item hint
A 1-item checklist gets a pill labelled Checklist: bg #eef6fb, color #2273bf, .7rem 600, radius 999px, padding .2rem .55rem, margin 0 0 .45rem. No progress header is rendered.
Reset control
A bordered pill button below the checklist: #fff fill, 1px #cbddea border, #2273bf text, radius 6px, padding .45rem .7rem. Hover: fill #eef6fb, border #9fc8e4, text #2597d5. It unchecks every item and clears localStorage for the post.
Alignment
Task lists sit flush against the left edge of the article column. The JS adds id86-task-list to every task <ul>, and CSS zeros out the default list indent:
.single-post .entry-content ul.id86-task-list {
margin-left: 0;
padding-left: 0;
}
Dark mode
The checklist ships its own @media (prefers-color-scheme: dark) rules (it does not depend on the DarkReader token swap):
- Marker: border
#3a4b68, fill#0b2149; hover border#6fc3f5; checked border#5aa7e8, check stroke#6fc3f5. - Row
is-done: bg#10294d, border#294b72, rail#6fc3f5, text#8aa3c0. - Row hover: bg
#162f55, border#3b638c, rail#6fc3f5. - Progress count: bg
#18365e, color#6fc3f5; bar track#24344f; fill#5aa7e8; complete#7fd493/#2f9e44. - Reset: fill
#0b2149, border#3a5a7d, color#5aa7e8; hover fill#162f55, border#6fc3f5.
Rendered DOM
js/article-content.js transforms the stored literal marker into this structure at render time:
<ul class="id86-task-list">
<div class="id86-task-progress" role="status" aria-live="polite">
<span class="id86-task-progress-count">2 of 5 done</span>
<span class="id86-task-progress-check" aria-hidden="true">✓</span>
<span class="id86-task-progress-bar"><span class="id86-task-progress-fill" style="width: 40%"></span></span>
<button type="button" class="id86-task-reset-group">Reset</button>
</div>
<li class="id86-task-item is-done">
<input type="checkbox" class="id86-task-marker" checked aria-label="Toggle task">
Install tmux from your package manager.
<p>Debian/Ubuntu and macOS (Homebrew) are both supported.</p>
</li>
<li class="id86-task-item">
<input type="checkbox" class="id86-task-marker" aria-label="Toggle task">
Verify tmux starts.
</li>
</ul>
<span class="id86-task-single-hint">Checklist</span>
<ul class="id86-task-list">
<li class="id86-task-item">
<input type="checkbox" class="id86-task-marker" aria-label="Toggle task">
One-off step with a hint pill and no progress header.
</li>
</ul>
<button type="button" class="id86-task-reset">Reset checklist</button>
Key behaviors in js/article-content.js:
- Detects
[ ]/[x](or[X]) as the first text node of an<li>, skipping anything insidepre, code, .wp-block-kevinbatdorf-code-block-pro. - Persists checked indexes under key
id86-tasklist:<postid>(derived from thepostid-<n>body class). Pre-checked[x]items start checked on first visit. - Adds
id86-task-itemto each row andid86-task-listto the parent<ul>. - Builds the progress header only when a group has ≥ 2 items; single-item lists get a
Checklisthint pill instead.updateProgress()re-renders the count, fill, checkmark, andis-completestate on every toggle. - Each progress header includes a per-group
Resetbutton that unchecks only that group's items and re-saveslocalStorage. - Appends one
Reset checklistbutton to the article body that clears storage and all checks across every group.
Full CSS
The complete, final checklist block (theme v1.20, css/article-content-v2.css):
.single-post .entry-content ul.id86-task-list {
margin-left: 0;
padding-left: 0;
}
.single-post .entry-content li.id86-task-item {
align-items: flex-start;
background: #fcfeff;
border: 1px solid #dbe8f2;
border-left: 3px solid #dbe8f2;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(11, 33, 73, .04);
list-style: none;
margin: 0 0 .55rem;
padding: .72rem .9rem;
transition: background-color .18s ease, border-color .18s ease,
box-shadow .18s ease, transform .18s ease;
}
.single-post .entry-content li.id86-task-item .id86-task-marker {
-webkit-appearance: none;
appearance: none;
box-sizing: border-box;
width: 19px;
height: 19px;
flex: 0 0 19px;
margin: 2px .65em 0 0;
padding: 0;
vertical-align: -4px;
border: 2px solid #b9c5d2;
border-radius: 5px;
background: #ffffff;
cursor: pointer;
transition: border-color .15s ease, background-color .15s ease;
}
.single-post .entry-content li.id86-task-item .id86-task-marker:hover {
border-color: #2273bf;
}
.single-post .entry-content li.id86-task-item .id86-task-marker:focus-visible {
outline: 2px solid #2597d5;
outline-offset: 2px;
}
.single-post .entry-content li.id86-task-item .id86-task-marker:checked {
border-color: #2273bf;
background-color: #e8f3fb;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232273bf' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
background-size: 12px 12px;
background-position: center;
background-repeat: no-repeat;
}
.single-post .entry-content li.id86-task-item.is-done {
background: #e8f3fb;
border-color: #c8e2f3;
border-left-color: #2273bf;
color: #7c8aa0;
text-decoration: line-through;
}
.single-post .entry-content li.id86-task-item p {
margin: .2em 0 0;
padding-left: 27px;
color: #61758a;
font-size: .92rem;
line-height: 1.55;
}
.single-post .entry-content li.id86-task-item:hover {
background: #f4faff;
border-color: #b9d8ec;
border-left-color: #2597d5;
box-shadow: 0 5px 14px rgba(11, 33, 73, .08);
transform: translateY(-1px);
}
.single-post .entry-content li.id86-task-item.is-done:hover {
background: #dceefa;
}
.single-post .entry-content .id86-task-progress {
display: flex;
align-items: center;
gap: .65rem;
margin: 1.25rem 0 .65rem;
max-width: 420px;
}
.single-post .entry-content .id86-task-progress-count {
background: #eef6fb;
border-radius: 999px;
color: #2273bf;
font-size: .75rem;
font-weight: 600;
letter-spacing: .01em;
padding: .25rem .55rem;
white-space: nowrap;
}
.single-post .entry-content .id86-task-progress-bar {
flex: 1 1 auto;
height: 7px;
border-radius: 999px;
background: #e2e9f1;
overflow: hidden;
}
.single-post .entry-content .id86-task-progress-fill {
display: block;
height: 100%;
width: 0;
background: #2273bf;
border-radius: 3px;
transition: width .25s ease;
}
.single-post .entry-content .id86-task-progress.is-complete .id86-task-progress-count {
color: #2f9e44;
}
.single-post .entry-content .id86-task-progress.is-complete .id86-task-progress-fill {
background: #2f9e44;
}
.single-post .entry-content .id86-task-reset {
display: inline-block;
margin: .65rem 0 0;
padding: .45rem .7rem;
border: 1px solid #cbddea;
border-radius: 6px;
background: #ffffff;
color: #2273bf;
font: inherit;
font-size: .85rem;
cursor: pointer;
text-decoration: none;
transition: background-color .18s ease, border-color .18s ease;
}
.single-post .entry-content .id86-task-reset:hover,
.single-post .entry-content .id86-task-reset:focus-visible {
background: #eef6fb;
border-color: #9fc8e4;
color: #2597d5;
}
@media (max-width: 600px) {
.single-post .entry-content li.id86-task-item {
padding: .68rem .72rem;
}
.single-post .entry-content .id86-task-progress {
gap: .45rem;
}
}
@media (prefers-color-scheme: dark) {
.single-post .entry-content li.id86-task-item .id86-task-marker {
border-color: #3a4b68;
background-color: #0b2149;
}
.single-post .entry-content li.id86-task-item .id86-task-marker:hover {
border-color: #6fc3f5;
}
.single-post .entry-content li.id86-task-item .id86-task-marker:checked {
border-color: #5aa7e8;
background-color: #0b2149;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236fc3f5' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
}
.single-post .entry-content li.id86-task-item.is-done {
background: #10294d;
border-color: #294b72;
border-left-color: #6fc3f5;
color: #8aa3c0;
}
.single-post .entry-content li.id86-task-item p {
color: #93a8c2;
}
.single-post .entry-content li.id86-task-item:hover {
background: #162f55;
border-color: #3b638c;
border-left-color: #6fc3f5;
}
.single-post .entry-content .id86-task-progress-count {
background: #18365e;
color: #6fc3f5;
}
.single-post .entry-content .id86-task-progress-bar {
background: #24344f;
}
.single-post .entry-content .id86-task-progress-fill {
background: #5aa7e8;
}
.single-post .entry-content .id86-task-progress.is-complete .id86-task-progress-count {
color: #7fd493;
}
.single-post .entry-content .id86-task-progress.is-complete .id86-task-progress-fill {
background: #2f9e44;
}
.single-post .entry-content .id86-task-reset {
background: #0b2149;
border-color: #3a5a7d;
color: #5aa7e8;
}
.single-post .entry-content .id86-task-reset:hover,
.single-post .entry-content .id86-task-reset:focus-visible {
background: #162f55;
border-color: #6fc3f5;
}
}
Where It Lives
| Concern | Location |
|---|---|
| Authoring syntax | Article Body Elements |
| Behavior | id86/js/article-content.js |
| Styles | id86/css/article-content-v2.css |
| Enqueue | functions.php — article CSS/JS loaded only on is_single() |
| Live demo (score 90) | https://id86.net/demo/checklist-ux-demo/ |
| Live demo (original) | https://id86.net/demo/tmux-setup-checklist/ |
Deployment Rules
- Any checklist/theme change requires the full cache purge chain, in order:
wp cache flush(as site user),sudo systemctl reload lsws,- Cloudflare
purge_everything.
?ver=alone is not enough — caches can ignore it. Renaming the asset file is the strongest fallback.- The reusable command sequence is captured in the AI-records registry record
20260810-05--id86-checklist-design(seescripts/deploy-checklist.sh). - The score-90 upgrade (per-group reset, single-item hint, completion checkmark) is recorded in the AI-records registry record
20260810-07--id86-checklist-score90.