components / table

Table

Product-grade data table with sorting, selection, sticky headers, and overflow-first responsiveness.

Overview

What it is

This section explains the intent of the component before the implementation details.

  • Table is the largest first-party collection surface in DK and is intentionally more like a product data table than a static layout demo.
  • It balances honest data structure, readable density, and practical interactivity without trying to become a spreadsheet.

Decision guide

How to choose it

Use these notes to decide quickly whether this is the right DK component for the job.

Decision guide

  • Choose Table when the interaction should feel deliberate, documented, and reusable across product surfaces.
  • If the scenario is more specialized than the current Table contract, prefer a simpler component or build a dedicated workflow on top of the lower-level DK primitives.

Do

  • Keep the Table label, value, or status language direct enough to scan quickly.
  • Use the documented size and state recipes instead of inventing one-off variants in product code.

Do not

  • Do not hide the main purpose of Table behind decorative copy or overloaded surface treatments.
  • Do not stretch Table into a workflow it was not designed to handle just because the visuals are close.

Usage

When to use

Prefer these situations when choosing this component.

  • Use Table for structured rows and columns where scanning, sorting, and selection matter.
  • Prefer the built-in scroll shell for narrow screens rather than inventing alternate mobile cards by default.

Usage

When not to use

These patterns are better served by a different component or a simpler surface.

  • Do not use Table for fully editable grids or spreadsheet-like workflows in this v1 contract.
  • Do not use Table when the dataset is so small that a simpler list or card set is clearer.

Anti-patterns

  • Using Table as a generic layout container instead of a purpose-built interaction or content surface.
  • Forking the documented recipe in product code instead of extending the shared DK contract.

Migration notes

  • Migrate legacy table usage by mapping existing variants and states onto the documented DK props before porting any custom styling.
  • Treat the docs examples as the reference contract for accessibility and event behavior during adoption.

Anatomy

Structural parts

The anatomy explains which pieces matter to the recipe and accessibility model.

scroll container

shell

Horizontal overflow shell for narrow viewports.

table header row

header

Column labels and sort affordances.

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 affordances.

API

Public interface

The docs contract distinguishes props, DOM events, and slots so integration behavior is explicit.

Props

NameTypeDefaultDescription
columnsTableColumn[]Column definitions including label, alignment, and sortability.
rowsTableRow[]Data rows, each with a stable `id`.
captionstringVisible caption describing the table.
descriptionstringOptional supporting description for the table context.
size'sm' | 'md''md'Chooses the table density recipe.
selectablebooleanfalseShows row selection affordances.
sortablebooleanfalseEnables sortable headers.
stickyHeaderbooleanfalsePins the header inside the scroll shell.
sortBystringControlled active sort column key.
sortDirection'asc' | 'desc''asc'Controlled sort direction.
selectedRowIdsstring[]Controlled selected row ids.
emptyTitlestringHeading for the empty state inside the table shell.
emptyDescriptionstringSupporting empty-state copy.
onSortChange(detail: { sortBy: string; sortDirection: "asc" | "desc" }) => voidCallback fired when sorting changes.
onSelectionChange(detail: { ids: string[] }) => voidCallback fired when row selection changes.
themeThemeContractOverrides the compiled DK theme used to resolve tokens and recipes for this component.

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 in v1 and does not expose cell or row slots.

Recipes

Variants, sizes, and states

These notes summarize the intended recipe surface rather than exposing raw implementation detail first.

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

Accessibility contract

This is the behavior the component promises to assistive tech and keyboard users today.

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.
  • Sticky headers and overflow shell should not block focus visibility.

Screen readers

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

Checklist

  • Verify the visible label or title still produces a clear accessible name.
  • Verify focus remains obvious in every supported state and size.
  • Verify disabled, selected, and error states do not rely on color alone.

Implementation

Tokens and slot vars

This section shows the representative compiled slot variables that the runtime consumes for the selected design system.

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 is intentionally a product-grade data table, not a full editable data grid or spreadsheet.

Implementation checklist

  • Use the public component API first and only drop to lower-level primitives when the component contract genuinely does not fit.
  • Keep theme overrides token-driven so proofs and recipes stay meaningful.
  • Verify the shipped examples and proof fixtures still represent the real product scenario you are implementing.

Examples

Copy-paste examples

Each example is intentionally practical, grouped by starter, common pattern, and edge-case coverage.

Examples
3
Depth
expanded

Starter

1 example

starter

Starter: sortable release list

A standard product data table with sortable headers.

Copy snippet

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

Common patterns

1 example

common

Common: selectable roster

Selection affordances support bulk or grouped action flows.

Copy snippet

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

Edge cases

1 example

edge

Edge: calm empty state

The table shell can stay honest even when there are no rows to show.

Copy snippet

<Table caption="Incidents" columns={teamColumns} rows={[]} emptyTitle="No incidents open" emptyDescription="Everything is stable right now." />

Verification

Curated proof fixtures

Proofs stay visible in the docs so the system shows what it can guarantee, not just what it can render.

default-md

pass

size=md

  • Contrast: 98.5 Lc, 98.5 Lc
  • Target: 44px, 44px
  • Layout: 0 / 320, 480, 760

dense-sm

pass

size=sm

  • Contrast: 98.5 Lc, 98.5 Lc
  • Target: 44px, 44px
  • Layout: 0 / 320, 480, 760

selected-md

pass

size=md

  • Contrast: 98.5 Lc, 97.9 Lc
  • Target: 44px, 44px
  • Layout: 0 / 320, 480, 760