Todo List
An agent's task plan: a <details>/<summary>wrapping a real <ol>. The plan is ordered work, so it's an ordered list, and the disclosure is native — the browser owns expand/collapse, keyboard support, and find-in-page expansion. The summary carries the completion count, because a collapsed plan whose progress you can't see is a plan you have to expand to learn anything from.
Fix the failing motion check1/4
- Read the failing testDone0.4s
- Trace the assertion to sheetDrag.tsIn progress
- Fix the settle targetTo do
- Re-run the motion checksTo do
Playground
Toggle every adjustable prop and watch the component — and the code — update live.
Plan2/4
- Read the failing testDone
- Trace the assertionDone
- Fix the settle targetIn progress
- Re-run the checksTo do
Usage
<TodoList label="Plan" metadata="4 steps" defaultOpen={true}>
<TodoItem status="done">Read the failing test</TodoItem>
<TodoItem status="done">Trace the assertion</TodoItem>
<TodoItem status="active">Fix the settle target</TodoItem>
<TodoItem status="pending">Re-run the checks</TodoItem>
</TodoList><kernel-todo-list label="Plan" metadata="4 steps">
<kernel-todo-item status="done">Read the failing test</kernel-todo-item>
<kernel-todo-item status="done">Trace the assertion</kernel-todo-item>
<kernel-todo-item status="active">Fix the settle target</kernel-todo-item>
<kernel-todo-item status="pending">Re-run the checks</kernel-todo-item>
</kernel-todo-list>Props
| Prop | Type | Default |
|---|---|---|
label | ReactNode | "Plan" |
metadata | ReactNode | — |
completed | number | derived |
total | number | derived |
defaultOpen | boolean | true |
open / onOpenChange | boolean / (open) => void | — |
| Prop | Type | Default |
|---|---|---|
status | "pending" | "active" | "done" | "error" | "pending" |
metadata | ReactNode | — |
statusLabel | string | from status |
The completion count is derived from direct TodoItemchildren. Only direct children are inspected — anything deeper is your own structure, and guessing at it would print numbers that silently disagree with the visible list. Pass completed andtotal yourself in those cases. The custom element derives the same count from a MutationObserver on its items'status attributes, so updating one task's status updates the summary with no extra bookkeeping.
Motion
All four status marks are rendered at once and cross-faded by CSS ondata-status. That's what makes a status change read as the same mark morphing rather than one icon being swapped for another — and it means a status update is a single attribute write, with no JavaScript in the transition at all. The count uses tabular figures so ticking 9/12 → 10/12 doesn't shift the label beside it.
Accessibility
- Every item carries its status as real, visually hidden text ("To do", "In progress", "Done", "Failed"). A shape and a colour are not a label — override the wording with
statusLabel. - The disclosure is a native
<summary>, so it's focusable, operable with Enter and Space, and announced as expandable without any ARIA. - Marks are aligned to the task's first line rather than centred on it, so a task that wraps to three lines still reads as one item.