Data Table

A captioned table with sortable columns, loading rows and an empty message.

This week’s orders
OrderCoffeeStatus
A-1042Linh TranEthiopia Guji2Roasting
A-1041Sam OkaforColombia Huila1Shipped
A-1039Ana RuizKenya Nyeri3Delivered
A-1036Minh PhamEthiopia Guji1Delivered
'use client';

import { Badge, DataTable } from '@brand-studio/ui';

interface Order { id: string; customer: string; coffee: string; bags: number; status: 'Roasting' | 'Shipped' | 'Delivered' }

const orders: Order[] = [
  { id: 'A-1042', customer: 'Linh Tran', coffee: 'Ethiopia Guji', bags: 2, status: 'Roasting' },
  { id: 'A-1041', customer: 'Sam Okafor', coffee: 'Colombia Huila', bags: 1, status: 'Shipped' },
  { id: 'A-1039', customer: 'Ana Ruiz', coffee: 'Kenya Nyeri', bags: 3, status: 'Delivered' },
  { id: 'A-1036', customer: 'Minh Pham', coffee: 'Ethiopia Guji', bags: 1, status: 'Delivered' },
];

export default function DataTableDemo() {
  return (
    <div style={{ width: '100%', maxWidth: 620 }}>
      <DataTable
        caption="This week’s orders"
        rows={orders}
        rowKey={(order) => order.id}
        columns={[
          { key: 'id', header: 'Order' },
          { key: 'customer', header: 'Customer', sortValue: (order) => order.customer },
          { key: 'coffee', header: 'Coffee' },
          { key: 'bags', header: 'Bags', align: 'end', sortValue: (order) => order.bags },
          { key: 'status', header: 'Status', cell: (order) => <Badge tone={order.status === 'Roasting' ? 'accent' : 'neutral'}>{order.status}</Badge> },
        ]}
      />
    </div>
  );
}

Installation

pnpm add @brand-studio/ui

Usage

import { DataTable } from '@brand-studio/ui';
<DataTable
  caption="This week’s orders"
  rows={orders}
  rowKey={(order) => order.id}
  columns={[
    { key: 'customer', header: 'Customer', sortValue: (order) => order.customer },
    { key: 'bags', header: 'Bags', align: 'end', sortValue: (order) => order.bags },
  ]}
/>

Examples

Loading and empty

Pass loading while rows load and empty for what to show when there are none.

Loading
OrderCustomerStatus
Refunds
OrderCustomerStatus

No refunds this month

Refunds you issue show up here.

'use client';

import { Button, DataTable, EmptyState } from '@brand-studio/ui';

const columns = [{ key: 'id', header: 'Order' }, { key: 'customer', header: 'Customer' }, { key: 'status', header: 'Status' }];

export default function DataTableStates() {
  return (
    <div style={{ display: 'grid', gap: 20, width: '100%', maxWidth: 560 }}>
      <DataTable caption="Loading" rows={[]} rowKey={() => ''} columns={columns} loading />
      <DataTable caption="Refunds" rows={[]} rowKey={() => ''} columns={columns} empty={<EmptyState title="No refunds this month" description="Refunds you issue show up here." action={<Button tone="secondary">Issue a refund</Button>} />} />
    </div>
  );
}

Accessibility

  • A real <table> with a <caption> and column headers. hideCaption keeps the caption for screen readers only.
  • Sortable headers are buttons, and the sorted column carries aria-sort.
  • On narrow screens the table scrolls sideways inside its own frame. The frame is focusable and named by the caption, so keyboard users can scroll it too.
  • loading sets aria-busy on the table.

API Reference

PropTypeDefault
captionstring
columnsTableColumn<object>[]
rowsobject[]
rowKey(row: object) => string
loading?boolean
empty?React.ReactNode
hideCaption?boolean
className?string

Still, Deskhand and Hollis are fictional brands. The coffee photographs are original generated artwork. @brand-studio/ui is MIT licensed.