Skip to content

Table

Rows and columns of data with sorting, selection, nested rows and drag and drop. Describe the columns up front, then render a TableRow for every item.

Image
Title
Status
Table.tsx
import {
  Badge,
  Table,
  TableCell,
  TableRow,
  TableThumbnail,
  TableTitle
} from 'alinea/components'

const products = [
  {id: 'oak-dining-chair', title: 'Oak dining chair', status: 'published'},
  {id: 'walnut-stools', title: 'Walnut stools', status: 'draft'},
  {id: 'linen-lounge-chair', title: 'Linen lounge chair', status: 'archived'}
] as const

export function TableExample() {
  return (
    <div style={{width: 520, height: 180}}>
      <Table
        aria-label="Products"
        items={products}
        columns={[
          {id: 'image', header: 'Image', width: 72},
          {id: 'title', header: 'Title', width: '1fr'},
          {id: 'status', header: 'Status', width: 120}
        ]}
      >
        {product => (
          <TableRow id={product.id} textValue={product.title}>
            <TableThumbnail src={`/catalog/${product.id}.jpg`} alt="" />
            <TableTitle title={product.title} label="Products" />
            <TableCell>
              <Badge size="sm" status={product.status}>
                {product.status}
              </Badge>
            </TableCell>
          </TableRow>
        )}
      </Table>
    </div>
  )
}

Selection and sorting

Set a selectionMode to let editors select rows, showSelectionControls adds a checkbox to every row. Mark columns as sortable and sort the items in onSortChange.

TableSelection.tsx
import {
  type SortDescriptor,
  Table,
  TableCell,
  TableRow,
  TableTitle
} from 'alinea/components'
import {useState} from 'react'

const products = [
  {id: 'shirt', title: 'Linen shirt', price: 89},
  {id: 'chair', title: 'Oak dining chair', price: 245},
  {id: 'stool', title: 'Walnut stool', price: 160},
  {id: 'lamp', title: 'Ceramic table lamp', price: 120}
]

export function TableSelectionExample() {
  const [sort, setSort] = useState<SortDescriptor>({
    column: 'title',
    direction: 'asc'
  })
  const sorted = [...products].sort((a, b) =>
    sort.column === 'price' ? a.price - b.price : a.title.localeCompare(b.title)
  )
  if (sort.direction === 'desc') sorted.reverse()
  return (
    <div style={{width: 520, height: 230}}>
      <Table
        aria-label="Products"
        items={sorted}
        columns={[
          {id: 'title', header: 'Title', width: '1fr', sortable: true},
          {
            id: 'price',
            header: 'Price',
            width: 120,
            align: 'end',
            sortable: true
          }
        ]}
        selectionMode="multiple"
        showSelectionControls
        defaultSelectedKeys={new Set(['chair'])}
        sortDescriptor={sort}
        onSortChange={setSort}
      >
        {product => (
          <TableRow id={product.id} textValue={product.title}>
            <TableTitle title={product.title} />
            <TableCell align="end">€{product.price}</TableCell>
          </TableRow>
        )}
      </Table>
    </div>
  )
}

Props

<Table>

PropTypeDefaultDescription
items*Iterable<T>Default –
columns*ReadonlyArray<TableColumn>Default –
showHeaderbooleanDefault trueShow the column headers, defaults to true
expandablebooleanDefault falseReserve space for expand toggles so titles line up, set when rows nest
rowHeightnumberDefault 44
selectionBehavior'toggle' | 'replace'Default 'toggle'How pointer clicks select rows. With toggle (the default) a click runs the row action, or toggles its selection while rows are selected. With replace a click selects only that row and a double click or Enter runs the row action.
showSelectionControlsbooleanDefault –Show a selection checkbox per row, defaults to multiple selection
variant'surface' | 'plain'Default 'surface'plain drops the rounded surface, eg. when the table fills a panel
expandedKeysIterable<Key>Default –
defaultExpandedKeysIterable<Key>Default –
onExpandedChange(keys: Set<Key>) => voidDefault –
sortDescriptorSortDescriptorDefault –
onSortChange(descriptor: SortDescriptor) => voidDefault –
onRowAction(key: Key) => voidDefault –Called when a row is activated (see selectionBehavior), rows can override it with their own onAction
renderEmptyState() => ReactNodeDefault –
dependenciesReadonlyArray<unknown>Default –Rows are cached per item, list the values children reads besides the item to render them again when those change
children*(item: T) => ReactElementDefault –

<TableRow>

PropTypeDefaultDescription
id*KeyDefault –
textValue*stringDefault –Text used for typeahead and as the accessible row name
hasChildrenbooleanDefault –Shows the expand toggle, also before the nested rows are loaded
rowsReactNodeDefault –Nested TableRows, rendered when the row is expanded. Pass them only once expanded to load them lazily, eg. from a component that renders the rows of already loaded data.
selectablebooleanDefault trueSet to false to disable selecting the row, its action still runs
highlightedbooleanDefault –Highlights the row, eg. to mark items that are already in use
onAction() => voidDefault –Called when the row is activated, overrides onRowAction
onClick() => voidDefault –Called on every click or tap of the row, next to its selection. Use it instead of onAction to act on a single click with the replace selection behavior, where onAction needs a double click.
onDoubleClick() => voidDefault –

Also accepts data-* attributes.

<TableCell>

PropTypeDefaultDescription
labelReactNodeDefault –A small caption above the value, like the dashboard explorer shows its columns when the header is hidden
align'start' | 'end' | 'center'Default –
titlestringDefault –Tooltip text

Also accepts className and style.

<TableThumbnail>

An image cell, sized to the row height

PropTypeDefaultDescription
srcstringDefault –
altstringDefault ''

Also accepts className and style.

<TableTitle>

PropTypeDefaultDescription
iconIconTypeDefault –Doubles as the drag handle of the row when rows can be dragged
title*ReactNodeDefault –
labelReactNodeDefault –A small caption above the title, eg. the parent path

Also accepts className and style.

Accessibility

Built on the React Aria grid: the arrow keys move between rows, Space selects a row and Enter runs its action. Sortable column headers announce their sort direction.