Skip to main content

What it is

A table shows many records of the same kind as rows, so a reader can scan them, compare them, sort them, select some, and act on one or many. It is the body of a list view, and it is the system’s only table. There is no lighter-weight table for simple cases: the system’s position, stated in a published guidance story, is that a table is built with the grid component rather than assembled from table primitives. That is what keeps column behaviour, sorting, selection, and keyboard focus the same on every list page in the product.

Live example

Do not copy this example’s props — see Open issues for which ones are stale, and reach for the ones this page documents instead.

Exports

Vocabulary

The system says table. Code says DataGridPro; the design library says Data Grid. Table is what people say in conversation, so it is the word this documentation leads with. Say table in a review and grid only when you mean the export. Say “pinned”, not “frozen”. The design library labels the state Frozen Left and Frozen Right; code calls the same thing pinned. Pinned describes what the reader did — held a column in place — and frozen suggests the data stopped updating. Say “row”, not “record”, in interface copy. Record is a data word; a reader sees rows.

Choose Table when

  • The page shows many instances of one kind of thing, and the reader’s job is to find, compare, or act on them.
  • Every row has the same fields available, so columns mean the same thing all the way down.
  • Scanning down one attribute across records is a real task — which is what a column is for.

Choose something else when

Anatomy

The design library models these as separate components — a header, a row, a column, a cell, and a header cell — which is why its vocabulary is more specific than the code’s. Its column types are checkboxes, overflow options, icons, chip, text only, link only, icon and text, icon and link; its cell content is single line or multi line; and a column can be pinned left or right. Those are the words to use when specifying a table, and each maps to a column definition in code.

Variants, sizes, and states

What the theme decides, and the whole of it: the table’s surface, the rule under the header and between rows, the type of a column header and a cell, the hover and selected row fill, the keyboard focus ring on a cell, and the colour of the sort icon. Row states: default, hover, selected, and cell focus. A hovered row and a selected row are given the same fill (known issue), which matters most on exactly the pages that have bulk actions. Density is out of scope for this documentation (TITAN-GAP-01), so no compact row height is specified here even though the grid accepts one.

Edge and failure states

Tokens

One component token exists — data-grid-focus-ring, the ring on a keyboard-focused cell. It is deliberately not an elevation shadow. Everything else the table draws comes from the border, background, text, and type foundations.

Composition

A table fills Contents and nothing else shares it. That is a list view decision: a page whose body is a table does not also carry a summary strip or a chart beside it, because the reader came to find a row. Filters and search sit above it. In the page frame they belong to Controls; in the design library’s table templates they are drawn inside the table’s own toolbar band. Both place them directly above the columns they narrow — see TITAN-DIV-18 for which owns them. Selection turns the toolbar into a selection bar. In the design library, selecting rows changes the toolbar band: it takes the selected fill, shows a count — “4 selected” — keeps search available, and reveals the bulk actions. The header checkbox carries a menu with Select all n items, Select all on page, and Deselect all, which is how the design distinguishes selecting a page from selecting a result set. None of this exists in codeTITAN-DIV-19, and it is the substance of Bulk selection. Row actions go in a trailing actions column, one per row, collapsed into a menu past two. A row action never duplicates the page’s primary action.

Content

  • Column headers are nouns, and name the value rather than the type. “Owner”, not “Owner name string”. Casing is this page’s best guess, not confirmed — Capitalization and punctuation lists table column headers explicitly as an open gap.
  • Do not repeat the table’s subject in every header. On a campaigns table, “Name” beats “Campaign name”.
  • Numbers align right, text aligns left, and a column of numbers uses one precision all the way down — see Numbers.
  • Dates use the system format, M/D/YYYY, never a relative string in a sortable column: “2 days ago” cannot be compared down a column — see Dates & times.
  • A row’s first cell identifies the record, and is the thing that links to it.
  • State the count with the noun — “147 campaigns”, not “147 results”.

Accessibility

The table needs an accessible name describing what it lists, and it needs to be the same thing the page title says. Sort state must be announced, not only drawn. A sorted column header carries its direction programmatically; a coloured arrow alone is invisible to a screen reader. Selection needs a live count. “4 selected” is the feedback for an action taken with a checkbox — without it, a keyboard user selecting rows gets no confirmation anything happened. Every checkbox needs a name that identifies its row. A column of controls all announcing “checkbox” cannot be used. The header checkbox is named for its scope, not “select all”, because what it selects is exactly the decision the design’s menu makes explicit. A row action’s accessible name includes the row. “Delete” repeated forty times is unusable out of context; “Delete Acme campaign” is not. Keyboard. Cells are individually focusable and carry data-grid-focus-ring when focused. A table with many rows is a large focus surface: provide a way past it, and never make a row action the only route to a function. Do not use a table for layout. Column and row semantics tell a screen-reader user that position carries meaning. If it does not, the structure is a lie.

Constraints

Retired

Known issues

Table: open issues

Divergences, open decisions, and undocumented gaps for Table.

Why it works this way

One table, not a family of them. The obvious alternative is a lightweight table for simple cases and the full grid for complex ones. Every page that starts simple acquires sorting, then selection, then pagination, and the migration between the two is a rewrite. Paying the grid’s cost up front is cheaper than paying it in a rewrite on a page that is already in production. Columns are the reason a table exists. A row of values is a list; a column is what lets someone compare one attribute across many records. Most rules on this page follow from that — why headers must stay readable while cells may truncate, why the identifying column gets pinned, why mixed record types break the format. Selection is a mode, so it needs its own bar. Once rows are selected, the reader’s next action is about the selection rather than about the page, and the actions available change. That is the argument for the design’s selection bar over a fixed row of bulk buttons: buttons that are permanently present but only sometimes meaningful get pressed when they are not.

Status and changelog

Last modified on September 7, 2026