Design Lounge
Install

Nº 100 of 520

Checkbox draw list

A ruled-paper to-do list where red-pencil checkmarks draw in, strike-throughs sweep across each task, and a 232px serif numeral counts progress.

Preview

Open full size
1280 × 800 · 10.7 KB · 135 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Checkbox draw list" (piece checkbox-draw-list) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

184 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

A pre-trip checklist in a fictional notes app, Margin. The list sits on a ruled sheet with a salmon margin line; each checkbox lives in the margin, and ticking it draws a red-pencil checkmark (280ms stroke) followed by a pencil line sweeping through the task text (360ms, 180ms later). A huge light-weight serif numeral on the left counts tasks done, with a row of nine tick segments underneath. It should feel like crossing things off a paper list: quiet, satisfying, slightly analogue. The detail worth copying is the sequencing: the check finishes before the strike starts, so the eye follows one stroke into the next.

Reference behaviour

  1. Initial state: nine tasks, five ticked (rows 1, 2, 3, 5, 7). Numeral reads “5”, suffix “of 9”, five red tick segments, note “4 left. Flight TP1351 leaves Thursday at 06:40.” Header right reads “5 done · 4 to go”.
  2. Click anywhere on a task row (the whole row is the <label>): the native checkbox toggles.
  3. Ticking: the 18px rounded square dims from ink to --ink-3; the checkmark path draws from 0 to full length in 280ms; after a 180ms delay a 1.5px red line scales from 0 to full width across the task text, origin left, over 360ms; the text colour fades to --ink-3.
  4. Unticking: checkmark retracts over 280ms and the strike retracts immediately (no delay) over 360ms; text returns to ink.
  5. Each change updates the numeral (it re-enters from 18px below at 20% opacity over 420ms), the “of N” suffix, the tick segments, the header summary and the note.
  6. When all tasks are done the note becomes “All set. Boa viagem — see you on the 15th.”
  7. Type in the “Add a task and press Enter” row and press Enter: a new unticked row (tag “New”) slides in from 8px above over 360ms; totals update. The list caps at 10 rows; the oldest-last row is dropped to keep the sheet in frame.
  8. “Untick all” in the footer clears every box, replaying all retract animations together.
  9. Hovering a row turns its checkbox square red.

Structure

1280 × 800, padding 48 64 48 72, grid 400px | 1fr
┌───────────────────────────┬───────────────────────────────────────────────┐
│ MARGIN          Sat 3 Oct │ ┌─────┬─────────────────────────────────────┐ │
│                           │ │     │ Packing & errands      5 done · 4…  │ │ 72
│ Before Lisbon  (64 ital.) │ ├─────┼─────────────────────────────────────┤ │
│ sub, 300px max            │ │ [✓] │ ~~Scan passport and send…~~   Docs  │ │ 56 each
│                           │ │ [ ] │ Change €60 into coins…   (Wed) Money│ │
│                           │ │  …  │  nine rows                           │ │
│ 5                         │ │  +  │ Add a task and press Enter          │ │
│   of 9   (232px / 48px)   │ │     │ hint                    Untick all  │ │
│ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬  ticks  │ └─────┴─────────────────────────────────────┘ │
│ 4 left. Flight…           │   margin line at x = 64px inside the sheet     │
└───────────────────────────┴───────────────────────────────────────────────┘
  • <aside class="side" aria-label="Progress">: brand row, <h1>, .sub, .big (numeral + suffix, aria-hidden), .ticks (aria-hidden), <p class="note" aria-live="polite">.
  • <main class="sheet">: header (<h2> + summary), <ul> of <li> rows, <form class="add">, footer.
  • Each <li>: visually hidden <input type="checkbox"> followed by a <label> containing .box (64px wide margin cell holding the SVG), .t (task text wrapped in <s> used only as the strike host), .meta (optional due pill + tag).

Tokens

:root {
  --bg: #ece6d8;        /* desk */
  --paper: #f8f4ea;     /* sheet */
  --rule: #dcd3c0;      /* row rules, pills, inactive ticks */
  --margin: #d98b7a;    /* vertical margin line, 70% opacity */
  --ink: #1f2a26;       /* text, box stroke */
  --ink-2: #55605a;     /* secondary */
  --ink-3: #8a8f86;     /* done text, meta, placeholder */
  --pencil: #c2412d;    /* check, strike, ticks on, focus */

  --serif: "Newsreader", Georgia, serif;
  --sans: "Work Sans", system-ui, sans-serif;
  --fs-numeral: 232px; --fs-of: 48px; --fs-h1: 64px; --fs-h2: 26px;
  --fs-task: 19px; --fs-sub: 17px; --fs-body: 15px; --fs-meta: 12px;

  --row: 56px;          /* task row height */
  --gutter: 64px;       /* margin column */
  --r-sheet: 4px; --r-box: 3px; --r-pill: 999px;

  --shadow-sheet: 0 1px 0 #d6cdb9, 0 2px 0 #e9e2d2, 0 3px 0 #d6cdb9, 0 24px 40px -24px rgba(60,40,10,.3);

  --t-fast: 160ms; --t-draw: 280ms; --t-strike: 360ms; --strike-delay: 180ms;
  --ease: cubic-bezier(.2,.7,.2,1);
  --ease-out: cubic-bezier(.16,1,.3,1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase / style
NumeralNewsreader (opsz 72)232px3000.8−0.06emlining figures
“of 9”Newsreader48px4000.80italic
h1Newsreader64px3001.0−0.02emitalic
SubNewsreader17px4001.450roman
Sheet titleNewsreader26px4001.1−0.01emroman
TaskNewsreader19px4001.20roman
Add inputNewsreader19px4001.20italic
BrandWork Sans12px6001+0.16emUPPERCASE
Note / bodyWork Sans14px400 (600 lead)1.450sentence
Meta, pillsWork Sans12px4001.20sentence

Motion

ElementTriggerPropertyFrom → ToDurationEasingDelay
Checkmark pathcheckstroke-dashoffset1 → 0 (pathLength 1)280ms--ease0
Checkmark pathuncheckstroke-dashoffset0 → 1280ms--ease0
Strike linechecktransform scaleX0 → 1, origin left360ms--ease180ms
Strike lineunchecktransform scaleX1 → 0360ms--ease0
Task texttogglecolorink ↔ ink-3360ms--ease0
Box squaretoggle/hoverstrokeink → ink-3 / pencil160mslinear-free default0
Numeralcount changetranslateY, opacity18px, .2 → 0, 1420ms--ease-out0
Tick segmentcount changebackgroundrule ↔ pencil360ms--ease0
New rowaddtranslateY, opacity−8px, 0 → 0, 1360ms--ease-out0

Reduced motion: durations 1ms, delays 0, keyframes removed. Ticks and strikes still appear, instantly.

States

  • Unchecked: ink square, no check, ink text.
  • Checked: --ink-3 square, red check, red strike, --ink-3 text.
  • Hover (row): square stroke turns --pencil.
  • Focus-visible: 2px --pencil outline 6px outside the checkbox SVG (the input is visually hidden, so style input:focus-visible + label .box svg).
  • Add row focused: row tints rgba(194,65,45,.04).
  • All done: note copy changes; every tick segment red.
  • Due soon: .meta em pill (1px --rule border, 999px radius, 2px 9px padding) before the tag.

Accessibility

  • Real <input type="checkbox">, visually hidden with opacity:0; width:1px; height:1px (not display:none), labelled by the whole row <label>. Space toggles when focused.
  • Tab order: tasks top to bottom, add input, “Untick all” button.
  • The <s> element is presentational here (no native line-through); if you prefer semantics, add aria-label="done" state via the checkbox, not the text.
  • The note is aria-live="polite", so totals are announced after each change; the numeral and ticks are aria-hidden duplicates.
  • Contrast: ink on paper 13:1; --ink-3 done text 3.2:1 is intentionally reduced and paired with the strike; keep active tasks in full ink.
  • Rows are 56px tall, the full row is clickable.

Responsive rules

  • ≥ 1280: as drawn.
  • 1024: left column stays 400px, sheet narrows; tasks ellipsize (white-space:nowrap; text-overflow:ellipsis).
  • < 1000: single column; numeral 140px; side panel stacks above the sheet; body scrolls.
  • < 640: hide the tag text, keep due pills; margin column shrinks to 52px.

Acceptance checklist

  • Five of nine tasks are ticked on load; numeral reads 5 and five of nine segments are red.
  • Checkmark draws via stroke-dashoffset with pathLength="1", over 280ms.
  • Strike starts 180ms after the check begins and sweeps left to right in 360ms.
  • Unticking retracts the strike with no delay.
  • The whole row toggles the checkbox; the checkbox is a native input.
  • Numeral, suffix, segments, header summary and note all update on every change.
  • Enter in the add row appends an unticked task with a slide-in; empty input does nothing.
  • “Untick all” clears every row.
  • Salmon margin line runs the full height of the sheet at x = 64px.
  • Focus ring is visible on every checkbox and the footer button.
  • Reduced motion keeps every state, without animation.

Implementation notes

Normalise the path length so one dash value works for any checkmark shape:

<svg viewBox="0 0 24 24">
  <rect class="sq" x="3" y="3" width="18" height="18" rx="3"/>
  <path class="ck" pathLength="1" d="M7 12.5l3.5 3.8L18.5 6"/>
</svg>
.ck { stroke: var(--pencil); stroke-width: 2.4; stroke-dasharray: 1; stroke-dashoffset: 1;
      transition: stroke-dashoffset var(--t-draw) var(--ease); }
input:checked + label .ck { stroke-dashoffset: 0; }

Asymmetric delay lives on the checked selector only, so it applies going in but not coming out:

.t s { position: relative; display: inline-block; text-decoration: none; }
.t s::after { content: ""; position: absolute; left: -3px; right: -3px; top: 56%; height: 1.5px;
  background: var(--pencil); transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-strike) var(--ease); }
input:checked + label .t s::after { transform: scaleX(1); transition-delay: var(--strike-delay); }

Restart the numeral animation by removing the class and forcing reflow:

num.classList.remove('bump'); void num.offsetWidth; num.classList.add('bump');

Common mistakes: using text-decoration: line-through (can’t animate); wrapping long tasks onto two lines so the strike only covers the first; display:none on the checkbox, which removes it from keyboard order.

Details

Palette
Type
Newsreader · Work Sans
Motion
Subtle motion
Build
Quick build
Designed bySusan AcharyaKathmandu

Studied

Tailwind CSS docs

A control. It studies this site for quiet, not for a show.

All sources