table cells
Table cells are three ready-made cells for the columns of a data table, so every table says the same things the same way. The status cell shows a status and its reason, the date cell a date and how long ago it was, and the identifier cell what names the row.
| episode | aired | state |
|---|---|---|
| S04E07The Long Way Round | , 5 days ago | missingnothing found · again in 6h |
| S04E08Low Tide | , yesterday | error: needs attentionno release passed the quality profile: every one was below 720p |
| S04E09 | no date | on its waywaiting for the catalog |
when to use
- Status cell. For the state column of a data table: one status per row, from the object's one table of states, with the reason or the next step under it when there is one.
- Date cell. For a date column where age matters: “aired”, “last searched”, “created”, “expires”.
- Identifier cell. For the row header column: what people call the row, with a quieter second line that says more.
when to use something else
- For a status outside a table, such as beside a heading, in a card or in a sentence. Use a status on its own. The status cell only adds the detail line, sized for a row.
- For a message about the whole list, such as “105 titles can't be checked”. That is a banner above the table, not a status in every row.
- For tags, kinds and categories. They aren't states. Use a badge.
- For numbers and sizes. Use a plain column, aligned to the right, with the unit in its header. The content rules of the table show how.
anatomy
status cell
- Wrapper. The status, with its detail close under it, at the cell's leading edge. It is never wider than the cell.
- Status. A status: a small chip in the state's colour, with the state's glyph and a few words in lowercase monospace, on one line.
- Detail (optional). A quieter line under the status, in small monospace, at most 44 characters wide. At most two lines show. It can break anywhere, so a long code can't widen the column.
date cell
- Wrapper. The date and how long ago it was, side by side on one line; in a narrow column, the second part wraps under the date. Both are in monospace with tabular figures, so dates line up down a column.
- Date. The date itself, in lowercase: “12 oct 2026”, with the day, the short month and the year. Resting the pointer on it shows the full weekday, date and time.
- How long ago (on unless you turn it off). In a quieter colour: “5d ago” on screen, “5 days ago” for a screen reader.
- Empty. A dash in the quieter colour, read as “no date”.
identifier cell
- Wrapper. The title and the text, one above the other, close together.
- Title. What people call the row, in monospace and the brightest text colour: “S04E07”, “worker-2”.
- Text (optional). A second line in the body face, smaller and quieter, on one line. It ends in an ellipsis when its box is narrower than the text.
variants and states
status cell
The state sets the colour, the glyph and what a screen reader hears first; there is no setting for the colour. The five states, as the status vocabulary uses them:
| state | means | for instance |
|---|---|---|
| Neutral | A plain fact | “missing” |
| Information | Under way, or worth knowing | “on its way” |
| Success | Done and good, and not for a problem | “held” |
| Warning | Works, but someone should look | “can't be checked” |
| Error | Failed or blocked; needs action | “needs attention” |
The detail is the line under the status: why it is so, or what happens next. Like every status, the one in a cell can also be shown as text: without the chip, for dense rows, keeping the glyph and slightly larger words.
date cell
How long ago counts whole calendar days, in the viewer's time zone:
| how far | on screen | read as |
|---|---|---|
| The same day | “today” | “today” |
| One day | “yesterday”, “tomorrow” | “yesterday”, “tomorrow” |
| 2 to 13 days | “5d ago”, “in 3d” | “5 days ago”, “in 3 days” |
| 14 to 59 days | “7w ago” | “7 weeks ago” |
| 60 days to just under two years | “20mo ago” | “20 months ago” |
| Two years or more | “2y ago” | “2 years ago” |
identifier cell
The title is what identifies the row; the text is a second line, when there is one to give. The title can be a link to the row's own page.
behaviour
- The cells are text. None of them takes focus or handles a key: the keys belong to the row they sit in, and the data table handles them. tab passes the cells by, unless an identifier's title is a link.
- The date cell works out “today” when it is drawn. It doesn't update on its own: a page left open past midnight says “today” about yesterday, until the cell is drawn again.
- The texts that show under the pointer, a status detail in full and a date's full time, are the browser's own tooltips.
The full keyboard map is on the table cells' React page.
accessibility
- Status cell. Screen readers skip the glyph: the words carry the state, and warnings and errors are read with their state first, as in “error: needs attention”. The detail is there in full for a screen reader, so it reads all of it, including the lines cut off on screen.
- Date cell. The date is read as it is written, “12 Oct 2026”, because the lowercase is only a style. Then comes how long ago, in words: “5 days ago”. Screen readers skip the short “5d ago”. An empty value is read as “no date”.
- Identifier cell. Plain text. As the row header, it names the row for every other cell in it.
Today:
- A status detail longer than two lines, and a date's full time, show only in the browser's own tooltip: under a mouse pointer. People who use a keyboard or a touch screen can't open it. Screen readers read the whole detail, but the time of day is only in that tooltip, which most of them don't read for plain text.
- A detail that isn't plain text, such as one that holds a link, gets no tooltip at all, so the lines that are cut off can't be seen on screen.
- The spoken words are fixed, in English: “no date”, and the words for how long ago, such as “5 days ago”, whatever the language of the dates.
- An identifier's second line that doesn't fit is cut short with an ellipsis, and has no tooltip.
content
- Status. The state in the object's own words, one to three words: “missing”, “on its way”, “needs attention”. Write the vocabulary once, as the status vocabulary describes, and use it in every row.
- Detail. Why, or what happens next, on one short line: “nothing found · again in 6h”, “waiting for the catalog”. A reason a person understands, not a raw error or a code.
- Dates. Give the cell the date itself and let it write the date out; don't turn the date into text beforehand. Keep how long ago where it matters how recent a date is, as for “aired” or “last searched”. Turn it off where it doesn't, as for a fixed release date.
- Identifier. The title is what people call the row, “S04E07” or “worker-2”; the text is its name, spelled the way the name is spelled: “The Long Way Round”. Don't use an internal id, a long string of letters and digits, as the title.
do and don't
related
- Data table: the table these cells are made for.
- Status: the status inside a status cell, on its own.
- Status vocabulary: how one table of states keeps every status cell honest.
specs
| cell | part | spec |
|---|---|---|
Status (StatusCell) |
Wrapper | A flex column with 3px between its lines, its items at the start, max-width: 100%, min-width: 0. |
| Status | Status | A status: a 20px chip with 8px side padding, a 1px --state-*-border, a --state-*-bg fill and --state-* text, the state's 12px glyph, JetBrains Mono 11px, lowercase, on one line. As text (appearance="text"): no chip, the glyph and 12px text. |
| Status | Detail | detail. JetBrains Mono 11px, line-height 1.45, --fg-dim, max-width: 44ch, clamped to two lines (-webkit-line-clamp: 2), overflow-wrap: anywhere. In the page in full. A string detail is also its title, the native tooltip; a detail that isn't a string gets none. |
Date (DateCell) |
Wrapper | inline-flex, wrapping, on one baseline, the parts 8px (--s-2) apart. JetBrains Mono 12px, font-variant-numeric: tabular-nums. |
| Date | Date | A <time> with the ISO datetime, in --fg-2, lowercased with text-transform. Its title holds the full weekday, date and time (dateStyle: 'full', timeStyle: 'short'). |
| Date | How long ago | relative (default true), in --fg-dim. The short form is aria-hidden; the long form is visually hidden. |
| Date | Empty | emptyText, by default an em dash (—), aria-hidden, in --fg-dim, and a visually hidden “no date”. |
Identifier (IdentifierCell) |
Wrapper | An inline-flex column with 2px between its lines, min-width: 0. |
| Identifier | Title | title, any node, a link included. JetBrains Mono 13px, --fg. |
| Identifier | Text | text. Inter 12px, --fg-muted, on one line (white-space: nowrap), with text-overflow: ellipsis and no tooltip. |
| date setting | in code | default |
|---|---|---|
| The date | value: a Date, an ISO string or epoch milliseconds |
An empty or unreadable value shows the empty text |
| Language of the date | locale |
'en-GB': “12 oct 2026” |
| Format | format, an Intl.DateTimeFormatOptions |
Day, short month, year: { day: 'numeric', month: 'short', year: 'numeric' } |
| How long ago | relative |
true |
| Today | now |
The current time; pinned in stories and tests |
- How long ago, the rule: whole calendar days in the viewer's time zone. Under 14 days it counts days, under 60 weeks (days ÷ 7), under 730 months (days ÷ 30.44), then years (days ÷ 365.25), each rounded.
relativeDay(then, now)is the same wording as a function, for a date outside a cell: it returns{ short: '5d ago', long: '5 days ago' }. - Fixed words:
emptyTextchanges only the visible dash, andlocaleonly the absolute date; “no date” and the words for how long ago stay English. - Status cell props: every status prop (
state,textor children,icon,appearance,stateLabel) anddetail. Spread a vocabulary entry in:<StatusCell {...BACKLOG_STATES[row.state]} detail={why(row)} />. - Identifiers: an internal id, such as a UUID, is never the title.
- Focus: none in any cell, unless an identifier's
titleis a link.