Skip to main content

Table

  • Component overview: Data table built around a data-driven columns + dataSource API. Layout, alignment, empty states, and merged cells should all be expressed through column and data configuration at the component layer.
  • Interaction: Row hover highlights, column alignment, and empty-state placeholder. Sorting, selection, and pagination are handled at the business layer.
  • Implementation note: ui/table retains the structural skeleton (scroll wrapper, border, hover); the design layer adds a data-driven rendering path on top.
  • Figma reference

Basic Usage

Pass columns and dataSource for automatic table rendering — the simplest way to use the component.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Name', dataIndex: 'name', width: 160 },
      { title: 'Email', dataIndex: 'email' },
      { title: 'Role', dataIndex: 'role', width: 120 },
    ]}
    dataSource={[
      { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Owner' },
      { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Editor' },
      { id: 3, name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer' },
    ]}
    rowKey="id"
  />,
)

Custom Render

Use the render function in column definitions to customize cell content.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Name', dataIndex: 'name', width: 160 },
      { title: 'Duration', dataIndex: 'duration', width: 100 },
      {
        title: 'Date created',
        dataIndex: 'createdAt',
        width: 180,
        render: (value) => new Date(String(value)).toLocaleDateString(),
      },
      {
        title: '',
        key: 'action',
        width: 60,
        align: 'center',
        render: () => (
          <button style={{ cursor: 'pointer', border: 'none', background: 'transparent' }}>
            ···
          </button>
        ),
      },
    ]}
    dataSource={[
      { id: 1, name: 'Meeting Notes', duration: '40m 39s', createdAt: '2026-03-10T17:47:03' },
      { id: 2, name: 'Project Review', duration: '25m 12s', createdAt: '2026-03-08T09:30:00' },
    ]}
    rowKey="id"
  />,
)

Column Alignment

Columns support left (default), center, and right alignment.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Item', dataIndex: 'item', align: 'left' },
      { title: 'Quantity', dataIndex: 'qty', align: 'center', width: 100 },
      { title: 'Price', dataIndex: 'price', align: 'right', width: 120 },
    ]}
    dataSource={[
      { id: 1, item: 'Plaud NotePin', qty: 2, price: '$169.00' },
      { id: 2, item: 'Plaud Note', qty: 1, price: '$159.00' },
    ]}
    rowKey="id"
  />,
)

Nested Data Index

Use an array for dataIndex to access nested object fields.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Name', dataIndex: 'name' },
      { title: 'City', dataIndex: ['address', 'city'] },
      { title: 'Country', dataIndex: ['address', 'country'] },
    ]}
    dataSource={[
      { id: 1, name: 'Alice', address: { city: 'Beijing', country: 'China' } },
      { id: 2, name: 'Bob', address: { city: 'Tokyo', country: 'Japan' } },
    ]}
    rowKey="id"
  />,
)

Header Tip

Set tip on a column to render the Figma-aligned help icon in the header and show explanatory content on hover.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Name', dataIndex: 'name', tip: 'Primary display name shown in the table.' },
      { title: 'Email', dataIndex: 'email' },
      {
        title: 'Status',
        dataIndex: 'status',
        tip: 'Status is synced from the latest review result.',
        width: 140,
      },
    ]}
    dataSource={[
      { id: 1, name: 'Alice Johnson', email: 'alice@example.com', status: 'Active' },
      { id: 2, name: 'Bob Smith', email: 'bob@example.com', status: 'Pending' },
    ]}
    rowKey="id"
  />,
)

Empty State

Set emptyText to show a placeholder when dataSource is empty.

Result
Loading...
Live Editor
render(
  <Table
    columns={[
      { title: 'Name', dataIndex: 'name' },
      { title: 'Email', dataIndex: 'email' },
    ]}
    dataSource={[]}
    emptyText="No data available"
  />,
)

Token Table

ElementPropertyToken / Value
HeaderText colorLabels/Tertiary#757575
HeaderHeight40px (h-10)
HeaderBottom border[&_tr]:border-b
RowHover backgroundBackgrounds/Tertiary (50%)rgba(0,0,0,0.03)
RowSelected backgroundBackgrounds/Tertiaryrgba(0,0,0,0.05)
CellPadding8px (p-2)
FooterBackgroundBackgrounds/Tertiary (50%)rgba(0,0,0,0.03)

Props

Table (data-driven)

PropTypeDefaultDescription
columnsTableColumnDef<T>[]-Column definitions
dataSourceT[]-Data array
rowKeystring | ((record: T) => Key)'key' or 'id'Unique row identifier
emptyTextReactNode-Content shown when dataSource is empty
classNamestring-Custom style class

TableColumnDef

FieldTypeDefaultDescription
keystring-Unique column key — falls back to dataIndex
dataIndexstring | string[]-Data field path — supports nested paths like ['address', 'city']
titleReactNode-Column header content
tipReactNode-Tooltip content rendered from the header help icon
sortablebooleanautoWhether the column renders sort affordance
render(value, record, index) => ReactNode-Custom cell renderer
widthnumber | string-Column width
align'left' | 'center' | 'right''left'Text alignment
classNamestring-Column className applied to both th and td