Skip to main content

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.

info

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)

FeatureWhat it does
Per-group ResetEach progress header carries its own Reset button that clears only that group and re-saves localStorage.
Completion checkmarkWhen a group is fully checked, a green badge scales in beside the "X of Y done" count and the bar flips green.
Single-item hintA 1-item checklist shows a Checklist pill instead of a progress header, so it is not mistaken for a plain bullet.
Global ResetThe 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).

PropertyValue
Idle background#fcfeff (almost white)
Border1px #dbe8f2
Left accent rail3px #dbe8f2 (idle)
Radius10px
Shadow0 2px 8px rgba(11, 33, 73, .04)
Margin / padding0 0 .55rem / .72rem .9rem (mobile .68rem .72rem)
Checkbox19px, 2px #b9c5d2 border, 5px radius, white fill

State colors

StateBackgroundLeft railNotes
Idle#fcfeff#dbe8f2default
Hover#f4faff#2597d5border #b9d8ec, lifted shadow 0 5px 14px rgba(11,33,73,.08), translateY(-1px)
Checked (is-done)#e8f3fb#2273bfborder #c8e2f3; text #7c8aa0 + line-through
Checked + hover#dceefa#2597d5via .is-done:hover

Checked marker: blue border #2273bf, brand-tint fill #e8f3fb, and an inline SVG checkmark (stroke #2273bf).

Progress header

PropertyValue
CountPill badge: bg #eef6fb, color #2273bf, .75rem 600, radius 999px, padding .25rem .55rem
Barflex: 1 1 auto, 7px, radius 999px, track #e2e9f1
Fill#2273bf, width transition .25s
Complete (is-complete)Count + fill turn green #2f9e44
Checkmark20px circle, bg #e8f3fb, color #2273bf, .8rem 700, hidden by default, scales in on .is-visible
Group resetBordered 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 inside pre, code, .wp-block-kevinbatdorf-code-block-pro.
  • Persists checked indexes under key id86-tasklist:<postid> (derived from the postid-<n> body class). Pre-checked [x] items start checked on first visit.
  • Adds id86-task-item to each row and id86-task-list to the parent <ul>.
  • Builds the progress header only when a group has ≥ 2 items; single-item lists get a Checklist hint pill instead. updateProgress() re-renders the count, fill, checkmark, and is-complete state on every toggle.
  • Each progress header includes a per-group Reset button that unchecks only that group's items and re-saves localStorage.
  • Appends one Reset checklist button 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

ConcernLocation
Authoring syntaxArticle Body Elements
Behaviorid86/js/article-content.js
Stylesid86/css/article-content-v2.css
Enqueuefunctions.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:
    1. wp cache flush (as site user),
    2. sudo systemctl reload lsws,
    3. 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 (see scripts/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.