Status & priority
StableTask glyphs told apart by shape: a square that fills as work moves on, priority bars, assignee, labels and estimate.
Status is a square that fills up (dashed, empty, half, three quarters, then solid with a tick or a cross); priority is a meter of one to three bars, with a solid “!” square for Urgent. Each glyph that carries meaning has a name that is read aloud and shown as its tooltip. They read their names from the sample workspace in product/data/sample.ts (statuses, priorities, people), so edit that file to make them yours.
Every status and priority
import { useId } from 'react'import { priorities, statuses, type PriorityId, type StatusId } from '@rchursin/product/data/sample'import { PriorityIcon, StatusIcon } from '@rchursin/product/tasks'
/** What each shape is, in words */const statusShape: Record<StatusId, string> = { backlog: 'dashed outline', todo: 'empty square', in_progress: 'half filled', in_review: 'three quarters', done: 'solid, tick cut out', canceled: 'solid, cross cut out',}const priorityShape: Record<PriorityId, string> = { 1: 'solid square with !', 2: 'three bars', 3: 'two bars', 4: 'one bar', 0: 'three dashes' }
/** Every status and priority. The square fills up as work moves on; priority is a meter of bars. */export default function StatusPriorityAll() { const statusId = useId() const priorityId = useId() return (Usage
import { AssigneeAvatar, EstimateBadge, LabelChips, PriorityIcon, StatusIcon } from '@rchursin/product/tasks'<StatusIcon status="in_review" /><PriorityIcon priority={1} />Examples
In a task row
All the parts in one row. Below 640 pixels the ID goes, and labels show from 768 pixels up.
import { labels, type Task } from '@rchursin/product/data/sample'import { SampleBadge } from '@rchursin/product/shell/layout'import { AssigneeAvatar, EstimateBadge, LabelChips, PriorityIcon, StatusIcon } from '@rchursin/product/tasks'
type Row = Pick<Task, 'id' | 'title' | 'status' | 'priority' | 'labels' | 'estimate' | 'assignee'>
const tasks: Row[] = [ { id: 'RC-128', title: 'Fix test-fixer: fails more than half its runs', status: 'in_progress', priority: 1, labels: ['skill', 'bug'], estimate: 3, assignee: 'me' }, { id: 'RC-125', title: 'Review pr-review prompt cost', status: 'in_review', priority: 2, labels: ['cost', 'agent', 'telemetry'], estimate: 2, assignee: 'tp' }, { id: 'RC-126', title: 'Merge openapi-writer into api-docs', status: 'backlog', priority: 4, labels: ['skill'], estimate: null, assignee: null }, { id: 'RC-120', title: 'Remove the duplicate code-explorer agent', status: 'canceled', priority: 0, labels: [], estimate: null, assignee: null },]
const labelName = (id: string) => labels.find((l) => l.id === id)?.name ?? id
/** * The parts together in a task row. Each named icon reads as "Status: In progress", "Priority: Urgent", * and is the tooltip too; labels and the estimate are spelled out for screen readers. */export default function StatusPriorityRow() {Assignee and estimate
An unassigned task gets a dashed empty square; a task without an estimate shows nothing (or the dashed glyph where a slot must stay).
import { AssigneeAvatar, EstimateBadge, EstimateGlyph } from '@rchursin/product/tasks'
/** * AssigneeAvatar looks a person up by id in the workspace; null draws a dashed empty square. * EstimateBadge shows points in the brand square and renders nothing for no estimate. */export default function StatusPriorityAssigneeEstimate() { return ( <dl className="grid grid-cols-[auto_1fr] items-center gap-x-6 gap-y-5 text-[13px]"> <dt className="text-fg-3">Assignee</dt> <dd className="flex flex-wrap items-center gap-2"> <AssigneeAvatar id="me" size="md" /> <AssigneeAvatar id="mq" size="md" /> <AssigneeAvatar id="tp" size="md" /> <AssigneeAvatar id="so" size="md" /> <AssigneeAvatar id={null} size="md" /> </dd> <dt className="text-fg-3">Estimate</dt> <dd className="flex flex-wrap items-center gap-2"> {[1, 2, 3, 5, 8].map((points) => ( <EstimateBadge key={points} value={points} /> ))} <span className="ml-2 inline-flex items-center gap-1.5 text-fg-3"> <EstimateGlyph value={null} /> None </span> </dd> </dl> )}API
StatusIcon props
| Prop | Type | Default | Description |
|---|---|---|---|
statusrequired | StatusId | – | 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled'. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'sm' | 12, 14, 16 or 20 pixels. |
decorative | boolean | false | Drop the name when text next to it already says the status. |
className | string | – | Extra classes on the <svg>. |
PriorityIcon props
| Prop | Type | Default | Description |
|---|---|---|---|
priorityrequired | PriorityId | – | 0 none, 1 urgent, 2 high, 3 medium, 4 low (Linear’s numbering). |
size | 'xs' | 'sm' | 'md' | 'lg' | 'sm' | 12, 14, 16 or 20 pixels. |
decorative | boolean | false | Drop the name (“Priority: High”, or “No priority”). |
className | string | – | Extra classes on the <svg>. |
AssigneeAvatar props
| Prop | Type | Default | Description |
|---|---|---|---|
idrequired | string | null | – | A person id from the workspace (people in data/sample.ts), or null for no assignee. |
size | 'xs' | 'sm' | 'md' | 'sm' | 16, 20 or 24 pixels. |
decorative | boolean | false | Drop the name and the tooltip when the name is written next to it. |
className | string | – | Extra classes on the avatar. |
LabelChips props
| Prop | Type | Default | Description |
|---|---|---|---|
idsrequired | string[] | – | Label ids. Renders nothing when empty. |
namerequired | (id: string) => string | – | Turns an id into its display name. |
max | number | 2 | Chips to draw before the rest become “+N”. |
className | string | – | Extra classes on the row. |
EstimateBadge and EstimateGlyph props
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | number | null | – | Points. EstimateBadge renders nothing for null or 0; EstimateGlyph draws a dashed empty square. |
className | string | – | Extra classes. |
Accessibility
- A named StatusIcon or PriorityIcon is an <svg role="img"> with a <title> (“Status: In review”, “Priority: Urgent”), which is both its accessible name and the hover tooltip. decorative makes it aria-hidden.
- AssigneeAvatar is role="img" named “Assignee: Mara Quinn” (“(you)” is added for the current user), with the name as its tooltip; no assignee is named “No assignee”.
- LabelChips hides the drawn chips and says them all once in hidden text, including the ones behind “+N”: “Labels: cost, agent, telemetry”.
- EstimateBadge says “Estimate: 3 points” in hidden text and as its tooltip; the square itself (EstimateGlyph) is always aria-hidden.
- Done and Canceled are both solid squares, told apart by a cut-out tick or cross; nothing depends on colour.
Guidelines
Do
- Keep the order the same in every row: priority, status, title, then the rest.
- Use decorative when the name is written beside the glyph.
- Write the name out in places people learn the shapes, like this page.
Don’t
- Don’t recolour a status to make it stand out; change its shape or add words.
- Don’t pass ids that aren’t in the workspace; AssigneeAvatar shows them as unassigned.
In use
Related
- Property pickersThe Tasks tool’s pickers for status, priority, assignee, labels and estimate, as a chip, a row or an icon.
- Task listThe task list as a data grid: one Tab stop, arrows to move, pickers in the cells, and groups that collapse.
- Task boardA column per status, with cards moved by dragging, by keys on their grip or from a menu, and every move announced.
- Due dateRelative due dates for task rows (“Today”, “Fri”, “Oct 30”) with overdue shown by shape, and a picker with quick choices.
- Icon guidelinesHow the icons are built, how status and priority are drawn, how to use icons, and how the set ships, as live sections.
Source
The component’s own files, exactly as they run on this site.
/** * Small task visuals. Status and priority are told apart by SHAPE (square outline, fill * amount, tick or cross; one to three bars, the "!" square), never by colour. Each icon that * carries meaning has a name ("Status: In review") that is also its tooltip. */import { cx } from "../../components/cx"import { personById, priorityById, statusById, type PriorityId, type StatusId,} from "../data/sample"import { Icon } from "../icons/Icon"import { Avatar } from "../ui/misc"import { dueInfo, estimateLong, personName } from "./model"
const iconSize = { xs: "size-3", sm: "size-3.5", md: "size-4", lg: "size-5" } as consttype IconSize = keyof typeof iconSize