Table

Data table with sorting, selection, and sticky headers.

Overview

  • Table uses row and column semantics. Horizontal scrolling preserves columns on narrow screens.

When to use

  • Use Table for structured rows and columns where scanning, sorting, and selection matter.
  • Use the scroll container to keep columns available on narrow screens.

When not to use

  • Do not use Table for fully editable grids or spreadsheet-like workflows.
  • Use a list or cards when users do not need row and column comparisons.

Structural parts

scroll container

shell

Horizontal overflow shell for narrow viewports.

table header row

header

Column labels and sort controls.

data row

row

Single record in the table body.

data cell

cell

Single value within a row.

optional checkbox column

selection

Select-all and row-selection controls.

Public interface

Props
NameTypeDefaultDescription
columnsTableColumn[]—Column definitions including label, alignment, and sortability.
rowsTableRow[]—Data rows, each with a stable id.
captionstring—Visible caption describing the table.
descriptionstring—Optional supporting description for the table context.
size'sm' | 'md''md'Chooses the table density recipe.
selectablebooleanfalseShows row selection controls.
sortablebooleanfalseEnables sortable headers.
stickyHeaderbooleanfalsePins the header inside the scroll shell.
sortBystring—Controlled active sort column key.
sortDirection'asc' | 'desc''asc'Controlled sort direction.
selectedRowIdsstring[]—Controlled selected row IDs.
emptyTitlestring'No rows'Heading for the empty state inside the table shell.
emptyDescriptionstring''Supporting empty-state copy.
onSortChange(detail: { sortBy: string; sortDirection: "asc" | "desc" }) => void—Runs when sorting changes.
onSelectionChange(detail: { ids: string[] }) => void—Runs when row selection changes.
themeThemeContract—Sets the theme used to resolve component styles.
Events
NamePayloadDescription
sortchange{ sortBy: string; sortDirection: "asc" | "desc" }Fires when sorting changes.
selectionchange{ ids: string[] }Fires when row selection changes.
Slots
NameDescription
defaultTable is data-driven and does not expose cell or row slots.

Variants, sizes, and states

Variants

  • sortable: Columns expose sort controls
  • selectable: Rows expose selection controls
  • sticky: Header remains visible in the shell

Sizes

  • sm: Dense data table
  • md: Default table density

States

  • sorted: Active sort applied
  • selected: Row selected
  • empty: No rows available
  • sticky: Header pinned in scroll shell

Accessibility

Semantics

  • Uses real table structure with caption, header relationships, and aria-sort for sortable headers.
  • Selection checkboxes remain clearly labeled at both row and table level.

Keyboard

  • Sort buttons and selection controls remain keyboard reachable in normal tab order.
  • Verify that keyboard focus remains visible inside the scroll container.

Screen readers

  • Caption, column headers, sort state, and row selection labels remain explicit.
  • The scroll shell is labeled so overflow remains understandable.

Checklist

  • Verify that the control has an accessible name.
  • Verify that keyboard focus is visible in every state and size.
  • Identify disabled, selected, and error states with text or symbols as well as color.

Tokens and CSS variables

root

--dk-table-gap
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-table-motion-duration
120ms

caption

--dk-table-caption-color
#16181c
--dk-table-caption-size
clamp(1.778rem, 1.6296rem + 0.658vw, 2.222rem)
--dk-table-caption-weight
650

description

--dk-table-description-color
#16181c
--dk-table-description-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)

shell

--dk-table-shell-bg
#f2f5fb
--dk-table-shell-fg
#16181c
--dk-table-shell-border
#95989d
--dk-table-shell-radius
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-table-shell-shadow
0 1px 2px rgba(0, 0, 0, 0.04)
--dk-table-shell-min-width
640px

headerCell

--dk-table-header-bg
#f2f5fb
--dk-table-header-fg
#16181c
--dk-table-header-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-table-header-weight
700
--dk-table-row-block-size
52px
--dk-table-inline-padding
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)

sortButton

--dk-table-sort-fg
#2072e4
--dk-table-sort-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-table-sort-target
44px

bodyRow

--dk-table-row-bg
transparent
--dk-table-row-fg
#16181c
--dk-table-row-hover-bg
#e5e8ed
--dk-table-row-selected-bg
#d7e9ff
--dk-table-row-selected-fg
#000f4d

cell

--dk-table-cell-color
#16181c
--dk-table-cell-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)

checkbox

--dk-table-checkbox-size
18px
--dk-table-checkbox-target
44px
--dk-table-checkbox-border
#95989d
--dk-table-checkbox-bg
#f2f5fb
--dk-table-checkbox-checked-bg
#2072e4

empty

--dk-table-empty-bg
#f2f5fb
--dk-table-empty-fg
#16181c
--dk-table-empty-title-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)

Implementation notes

  • Table does not provide cell editing.

Examples

Starter

Sortable release list

A standard product data table with sortable headers.

<Table caption="Release schedule" columns={releaseColumns} rows={releaseRows} sortable={true} stickyHeader={true} />

Common patterns

Selectable roster

Select rows for bulk actions.

<Table caption="Launch support roster" columns={teamColumns} rows={teamRows} selectable={true} sortable={true} selectedRowIds={['team-a', 'team-c']} />

Edge cases

Empty table

Provide guidance when users can resolve the empty condition.

<Table caption="Incidents" columns={teamColumns} rows={[]} emptyTitle="No incidents open" emptyDescription="No incidents require action." />

Mathematical fixtures

Calculated fixtures; browser evidence is not collected. Layout estimates use declared text behavior and maximum token values.

default-md

pass

size=md

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 98.5 Lc, 98.5 Lc
  • Target: 44 px, 44 px
  • Layout: shell · single-line 0 px single-line estimate; 0 px minimum width
    320 px: pass 480 px: pass 760 px: pass

dense-sm

pass

size=sm

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 98.5 Lc, 98.5 Lc
  • Target: 44 px, 44 px
  • Layout: shell · single-line 0 px single-line estimate; 0 px minimum width
    320 px: pass 480 px: pass 760 px: pass

selected-md

pass

size=md

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 98.5 Lc, 97.9 Lc
  • Target: 44 px, 44 px
  • Layout: shell · single-line 0 px single-line estimate; 0 px minimum width
    320 px: pass 480 px: pass 760 px: pass