Table

Table renders typed rows as a data table. Columns describe how each cell is rendered, so the same row objects can carry plain values, pills or any other content.

import { Table } from 'jarvis-react-ui';

Columns and rows

Table is generic over the row type. Each TableColumn<T> has a unique key, a header and a render(row, index) function that returns the cell content. align sets the alignment of header and cells (right-align numbers), and className is applied to every cell of the column, which is handy for widths. getRowKey must return a stable, unique key per row.

import { Pill, Table } from 'jarvis-react-ui';
import type { TableColumn } from 'jarvis-react-ui';

interface Subsystem {
    id: string;
    name: string;
    status: 'online' | 'degraded' | 'offline';
    load: number;
}

const ROWS: Subsystem[] = [
    { id: 'pwr', name: 'Power core', status: 'online', load: 72 },
    { id: 'com', name: 'Comms array', status: 'degraded', load: 38 },
    { id: 'nav', name: 'Navigation', status: 'online', load: 15 },
    { id: 'def', name: 'Defense grid', status: 'offline', load: 0 },
];

const STATUS_PILL = { online: 'ok', degraded: 'warn', offline: 'err' } as const;

const COLUMNS: TableColumn<Subsystem>[] = [
    { key: 'name', header: 'Subsystem', render: (row) => row.name },
    {
        key: 'status',
        header: 'Status',
        align: 'center',
        render: (row) => <Pill variant={STATUS_PILL[row.status]}>{row.status}</Pill>,
    },
    {
        key: 'load',
        header: 'Load',
        align: 'right',
        className: 'w-[80px] tabular-nums',
        render: (row) => `${row.load}%`,
    },
];

export default function Basic() {
    return (
        <Table
            caption="Subsystem status"
            columns={COLUMNS}
            rows={ROWS}
            getRowKey={(row) => row.id}
        />
    );
}

Define columns outside the component (or memoize them) so they are not recreated on every render. TypeScript infers T from rows and checks every render function against it.

Dense with a visible caption

dense tightens the cell padding for compact lists such as logs. The caption is always present for screen readers; showCaption also shows it as a small heading above the table.

import { Table } from 'jarvis-react-ui';
import type { TableColumn } from 'jarvis-react-ui';

interface LogEntry {
    time: string;
    source: string;
    message: string;
}

const ROWS: LogEntry[] = [
    { time: '12:04:31', source: 'uplink', message: 'Link established' },
    { time: '12:04:32', source: 'auth', message: 'Handshake ok' },
    { time: '12:04:33', source: 'telemetry', message: 'Streaming started' },
];

const COLUMNS: TableColumn<LogEntry>[] = [
    { key: 'time', header: 'Time', className: 'w-[90px] text-text-muted', render: (r) => r.time },
    {
        key: 'source',
        header: 'Source',
        className: 'w-[110px] text-accent',
        render: (r) => r.source,
    },
    { key: 'message', header: 'Message', render: (r) => r.message },
];

export default function DenseWithCaption() {
    return (
        <Table
            caption="Event log"
            showCaption
            dense
            columns={COLUMNS}
            rows={ROWS}
            getRowKey={(r, index) => `${r.time}-${index}`}
        />
    );
}

Empty state

When rows is empty, a single full-width row shows emptyText (default "No data").

import { Table } from 'jarvis-react-ui';
import type { TableColumn } from 'jarvis-react-ui';

interface Alert {
    id: string;
    level: string;
    message: string;
}

const COLUMNS: TableColumn<Alert>[] = [
    { key: 'level', header: 'Level', render: (r) => r.level },
    { key: 'message', header: 'Message', render: (r) => r.message },
];

export default function Empty() {
    return (
        <Table
            caption="Open alerts"
            columns={COLUMNS}
            rows={[]}
            getRowKey={(r) => r.id}
            emptyText="No open alerts. All systems nominal."
        />
    );
}

The API tables on every component page of this site are rendered with Table.

Accessibility

  • Renders a native <table> with <thead>, <tbody> and <th scope="col"> header cells.
  • Always pass a caption: it names the table for assistive technology. Without showCaption it is visually hidden but still read out.
  • The table sits in a horizontally scrollable container, so wide tables do not break the layout on small screens.
  • Rows are not interactive. If a row needs an action, put a button or link into a cell.

API

Prop Type Default Description
columns (required) TableColumn<T>[] Column definitions in display order.
getRowKey (required) (row: T, index: number) => string Returns a stable unique key for a row.
rows (required) T[] Data rows.
caption ReactNode Accessible caption, rendered visually hidden unless showCaption is set.
className string Additional class names for the scroll container that wraps the <table>.
dense boolean false Tighter cell padding.
emptyText ReactNode No data Content shown in a single full-width row when rows is empty.
showCaption boolean false Shows the caption above the table.

Type TableColumn<T>

Definition of one Table column; T is the row type.

Field Type Default Description
header (required) ReactNode Header cell content.
key (required) string Unique key of the column; used as the React key of its cells.
render (required) (row: T, index: number) => ReactNode Renders the cell content for a row; index is the row's position in rows.
align TableAlign 'left' Horizontal alignment of header and cells.
className string Extra class name for every cell in this column, e.g. one that sets the column width.

Type TableAlign

Horizontal alignment of a Table column.

type TableAlign = 'left' | 'center' | 'right';