Patterns

Tasks & Projects

A Linear-style tracker, assembled from the package rather than re-drawn: a filterable task table, a detail view you can edit in place, a board, a sub-task checklist, cycle metrics and a roadmap.

Every demo below is wired to real state — sorting sorts, filters filter, selection selects, and renaming a task changes the row you renamed. Only two things are drawn by hand, because the package ships neither: the board's columns and the roadmap's quarter grid. Everything inside them is still made of StatusPill, AvatarGroup and RelativeTime.

Task list

The screen most task apps are mostly made of. SearchFilterBar and FilterChips above, DataTable in the middle, TablePagination below — plus a column menu, bulk actions behind a ConfirmDialog, and InlineEdit in the title cell so a task can be renamed without leaving the list. Chip counts come from the set filtered by everything except status, so each chip answers “how many will I get”.

AssigneesActions
DS-413In progressMedium
Sep 3
DS-404In progressHigh
Sep 1
DS-401In progressUrgent
Aug 28
DS-402In reviewHigh
Sep 2
DS-415In reviewHigh
Aug 31

Task detail

Description on the left, properties on the right — and every property is a real control. InlineEdit renames the task (Enter saves, Esc reverts) and edits the description in multiline mode, where Enter adds a line and ⌘↵ saves. Status and priority run through Combobox, assignees through MultiCombobox, and the due date through DatePicker with an English locale and English labels. Each one sits in a FormField, which is what turns the caption into a real <label> and hands the control its id.

DS-401In progressUrgentTokens
Save Cancel 62Updated

Kanban board

The one screen here with no package equivalent — columns and cards are the app's own layout. What the app should not re-invent is what goes inside a card: the priority chip is a StatusPill, the faces are an AvatarGroup, the timestamp is a RelativeTime, and an empty column is an EmptyState. Column dots take their colour from getStatusConfig rather than a local switch. Move a card with the arrows — counts update as it lands.

Todo

3
DocsMedium

Document the labels mechanism end to end

11
DocsHigh

Replace the hand-rolled tables in Patterns

20
ComponentsMedium

Keyboard affordance for moving kanban cards

52

In progress

2
TokensUrgent

Re-map muted-foreground so AA holds on cards

62
ComponentsHigh

Pin the first DataTable column below md

93

In review

2
ComponentsHigh

Ship the async loading state for Combobox

30
TokensUrgent

Audit every focus ring against 1.4.11

41

Done

2
ToolingHigh

Publish 0.9.1 with the StatusPill border fix

71
DocsMedium

Give TablePagination English labels in the docs

20

Sub-task checklist

Real Checkbox controls with real <label> elements, and a real Progress — not a div with a computed width. The bar carries role="progressbar" and the track-to-bar contrast pair the package already measured, and it flips to the success tone at 100%.

Sub-tasks

2/5

Sub-tasks roll up to the parent task's progress.

Cycle progress

Four StatCard tiles plus a completion bar. The numbers are derived from the same task array the list above starts from — hard-coded KPI literals go stale the first time the data moves. Note polarity on the overdue tile: rising overdue work is bad news no matter which way the arrow points, and the delta colour has to say so.

Scope

16

Aug 24 – Sep 6

In flight

6

+2since Monday

Completed

3

+3since Monday

Overdue

2

+1since Monday

Cycle 12

At risk
3 of 16 done · 19%

Roadmap timeline

Quarter-scale planning above the task level. The grid and the bar placement are the app's, but the bar itself is a Progress in a positioned wrapper, the health flag is a StatusPill with a shortLabel, and the crew is an AvatarGroup. The row scrolls inside its own container so a long horizon never scrolls the page.

Q3 2026Q4 2026Q1 2027Q2 2027
Design system 2.0ON
72%
Shell and display axesRISK
38%
Public component APIPLAN
8%
+1

Best Practices

Do

  • Declare status and priority once as StatusConfig[] and let every surface read from it — table, chips, board, detail.
  • Give every composite that takes labels an English set; the package defaults are Vietnamese, screen-reader strings included.
  • Reset to page 1 whenever a filter changes, or a stale page reads as “no data”.
  • Count chips against everything except the filter they control, so each one predicts its own result.
  • Write two empty states — nothing yet, and nothing matched — and branch on the unfiltered set.
  • Give every task a short, stable key (DS-401) and keep it pinned when the table scrolls.
  • Confirm bulk deletes and single deletes with the same dialog, so both read the same weight.

Don't

  • Do not hand-roll a table. DataTable already has sorting, selection, pinning, column visibility, skeletons and an empty state.
  • Do not build class names from data (bg-${tone}-300) — Tailwind scans statically and purges what it cannot see.
  • Do not colour text with a fill token. text-destructive fails contrast; text-destructive-ink is the text step.
  • Do not nest a ConfirmDialog inside a DropdownMenu; the menu unmounts before the dialog opens.
  • Do not encode status or priority with colour alone — every chip carries its label too.
  • Do not caption a control with a styled <p>. FormField emits a real <label htmlFor>; a paragraph is not a label, however much it looks like one.
  • Do not make moving a card drag-only, and do not lose scroll position when one lands.
  • Do not put a control in a table cell without stopping its click from reaching the row.

Accessibility

  • Board cards move by button as well as by drag, and each button names its destination in aria-label.
  • Rows with onRowClick join the tab order and answer Enter/Space — the package does that for you, but only if you pass the handler.
  • Checklist items use the package's Checkbox — a Radix <button role="checkbox"> carrying aria-checked, not an <input> — tied to a real <label htmlFor>, so clicking the text toggles it and a screen reader announces the state.
  • Progress bars carry an aria-label with the percentage; the warning tone adds hazard stripes because gold and yellow are indistinguishable on three of the four palettes.
  • KeyboardHint is aria-hidden — the glyphs read as noise, so the button it decorates has to name its own shortcut.
  • Avatars in a group are focusable buttons carrying the member's name, so the group is not a row of silent colour swatches.
  • Combobox and MultiCombobox default the clear button's sr-only label to Vietnamese (“Xóa lựa chọn” / “Xóa tất cả lựa chọn”); pass clearLabel, added in 1.0.0, to name it in English — MultiCombobox needs it most, since clearable defaults to true there.
  • Announce bulk results and card drops through a live region, not only by re-rendering the list.

Built from

For the frame these screens sit in — sidebar, app header, workspace switcher, command palette — see the application pattern. For the same table shape against a different domain, see CRM and Commerce.