Terracotta
GitHub

A plain <table> is easier to use when the user only reads it. Reach for a grid when the cells are interactive, or when the table is large enough that moving cell by cell helps.

import {
  Grid,
  GridBody,
  GridCell,
  GridColumnHeader,
  GridFoot,
  GridHead,
  GridRow,
  GridRowHeader,
} from 'terracotta/grid';

Anatomy

<Grid>                    {/* <table role="grid">, the tab stop */}
  <GridHead>              {/* <thead role="rowgroup"> */}
    <GridRow>             {/* <tr role="row"> */}
      <GridColumnHeader/> {/* <th role="columnheader"> */}
    </GridRow>
  </GridHead>
  <GridBody>              {/* <tbody role="rowgroup"> */}
    <GridRow>
      <GridRowHeader/>    {/* <th role="rowheader"> */}
      <GridCell/>         {/* <td role="gridcell"> */}
    </GridRow>
  </GridBody>
  <GridFoot/>             {/* optional; <tfoot role="rowgroup"> */}
</Grid>

The parts render table elements by default, so the grid lays out its rows and columns with no CSS. Rows can also sit directly in the Grid, without a group.

Examples

Basic

What a cell should hold

The pattern names two cell designs that work best, and the grid supports both:

  • A cell with text, or a single image. Focus lands on the cell.
  • A cell with one widget that does not need the arrow keys, such as a link, a button, a checkbox or a switch. Focus lands on the widget, so screen readers announce it as a widget.

A cell with several widgets, or with a text field, takes focus itself. Tab then moves to the widgets inside it, and on out of the grid. The arrow keys are left to a text field, select or editable content inside a cell, so its caret still moves. Entering and leaving a cell with Enter and F2 is not included.

Naming and sorting

Name the grid with aria-label or aria-labelledby. Pass aria-sort on the header of a sorted column, as the demo does.

Layout grids

wrap lets the arrow keys wrap from the end of a row to the next row, and from the bottom of a column to the next column. Use it for a layout grid, such as a list of recipients that wraps across lines. Do not use it for data, where wrapping is disorienting.

Other elements

Every part takes as, so a grid can be built from divs. The roles are set on every part, so this works the same, though the layout is then yours to write:

<Grid as="div" aria-label="Recipients" wrap>
  <GridRow as="div">
    <GridCell as="div"><a href="/ada">Ada</a></GridCell>
  </GridRow>
</Grid>

State attributes

Element Attribute Present when
Grid tc-grid Always
GridHead, GridBody, GridFoot tc-grid-head, tc-grid-body, tc-grid-foot Always
GridRow tc-grid-row Always
GridCell tc-grid-cell Always
GridColumnHeader tc-grid-column-header Always
GridRowHeader tc-grid-row-header Always

Keyboard

Key Action
→ / ← Next / previous cell in the row. Stops at the edge, or wraps with wrap.
↓ / ↑ Cell below / above. Stops at the edge, or wraps with wrap.
Page Down / Page Up Moves pageSize rows down / up
Home / End First / last cell in the row
Ctrl+Home / Ctrl+End First cell of the grid / last cell of the grid
Tab Moves into the grid, on the cell that last had focus, else the first cell

The pattern's optional selection keys are not included. Set aria-selected on cells or rows yourself if the grid selects.

API

<Grid>

Renders a <table> by default, with role="grid".

Prop Type Default Description
as ValidConstructor 'table' Element or component to render as.
wrap boolean false Lets the arrow keys wrap, for a layout grid.
pageSize number 5 Rows moved by Page Up and Page Down.
ref DynamicNode<T> | (el) => void none Handle to the rendered element.
…rest props of as none Forwarded to the rendered element.

The grid sets tabindex on the cells, and on the widgets inside them, to keep one tab stop. A widget's own tabindex is put back when it leaves the grid.

Row groups, rows and cells

Component Renders Role
<GridHead> <thead> rowgroup
<GridBody> <tbody> rowgroup
<GridFoot> <tfoot> rowgroup
<GridRow> <tr> row
<GridCell> <td> gridcell
<GridColumnHeader> <th> columnheader
<GridRowHeader> <th> rowheader

Each takes as and forwards the rest of its props.