Home / Docs / PixelTable PixelTable Generic data table with striped rows, hover highlight, controlled sorting, single/multi row selection, sticky headers, density, loading skeletons and empty state.
Controlled sort with header buttons + aria-sort semantics Single or multi-row selection with checkbox column and indeterminate state Sticky header and sticky first column for wide datasets Built-in skeleton loading rows and configurable empty state Three density presets (compact, normal, comfortable) on pixel or linear surface Status stable Category data Since v1.0.0 API React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';Prop Type Default Description columns* (required) Array<PixelTableColumn<Row>>— Column definitions. data* (required) Row[]— Row data. stripedbooleantrueAlternate-row tint. Defaults to true. surface'pixel' | 'linear'— Visual surface override. sortPixelTableSortState— Controlled sort state. onSortChange(next: PixelTableSortState) => void— Called when a sortable header is clicked. selection'single' | 'multi'— Enables a leading checkbox column with single- or multi-row selection. selectedIdsstring[]— Controlled list of selected row ids. onSelectionChange(next: string[]) => void— Called when selection changes. getRowId(row: Row, idx: number) => string— Resolves a stable id per row. Falls back to row.id then to the index. stickyHeaderboolean— Sticks the header row to the top of the scroll container. stickyFirstColumnboolean— Sticks the first column to the left of the scroll container. loadingboolean— Renders skeleton rows instead of data. emptyStateReact.ReactNode— Rendered inside a full-width cell when data is empty. onRowClick(row: Row, idx: number) => void— Called when a body row is clicked, or on Enter / Space on it. Renders it focusable, with cursor-pointer. density'compact' | 'normal' | 'comfortable''normal'Cell padding scale. Defaults to 'normal'. borderedbooleantrueRender with surface-aware border + radius chrome. Defaults to true — tables need visible chrome.
Accessibility WCAG target: 2.1 AA
Uses native <table>, <thead>, <tbody>, <th scope="col"> and aria-sort on sortable columns. Loading state exposes role="status" with aria-live="polite". The multi-select header checkbox reflects indeterminate state when only some rows are selected.
Keyboard Key Action When Enter Activates a sortable column header button, or the focused clickable row (onRowClick) — Space Toggles the selection checkbox in the selection column, or activates the focused clickable row — Tab Moves focus between header sort buttons, selection checkboxes and clickable rows —
Usage React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function Default() {
return <PixelTable<Row> columns={baseColumns} data={rows} />;
}Examples Default React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function Default() {
return <PixelTable<Row> columns={baseColumns} data={rows} />;
}Striped React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function Striped() {
return <PixelTable<Row> columns={baseColumns} data={rows} striped />;
}Pixel surface React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function PixelSurface() {
return <PixelTable<Row> columns={baseColumns} data={rows} surface="pixel" />;
}Linear surface React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function LinearSurface() {
return <PixelTable<Row> columns={baseColumns} data={rows} surface="linear" />;
}Sortable columns React Vue Angular
import * as React from 'react';
import { PixelTable, type PixelTableSortState } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
export function Sortable() {
const [sort, setSort] = React.useState<PixelTableSortState>({ key: 'name', dir: 'asc' });
const sortable = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'role', header: 'Role', sortable: true },
{ key: 'status', header: 'Status' },
];
return (
<PixelTable<Row>
columns={sortable}
data={rows}
sort={sort}
onSortChange={setSort}
/>
);
}Single selection React Vue Angular
import * as React from 'react';
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function SingleSelection() {
const [selected, setSelected] = React.useState<string[]>(['1']);
return (
<PixelTable<Row>
columns={baseColumns}
data={rows}
selection="single"
selectedIds={selected}
onSelectionChange={setSelected}
/>
);
}Multi selection React Vue Angular
import * as React from 'react';
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function MultiSelection() {
const [selected, setSelected] = React.useState<string[]>([]);
return (
<PixelTable<Row>
columns={baseColumns}
data={rows}
selection="multi"
selectedIds={selected}
onSelectionChange={setSelected}
/>
);
}Compact density React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function CompactDensity() {
return <PixelTable<Row> columns={baseColumns} data={rows} density="compact" />;
}Comfortable density React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function ComfortableDensity() {
return <PixelTable<Row> columns={baseColumns} data={rows} density="comfortable" />;
}Loading React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function Loading() {
return <PixelTable<Row> columns={baseColumns} data={[]} loading />;
}Empty state React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function Empty() {
return (
<PixelTable<Row>
columns={baseColumns}
data={[]}
emptyState={<span>No records found.</span>}
/>
);
}Custom cell render React Vue Angular
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
export function CustomRender() {
const columns = [
{ key: 'name', header: 'Name' },
{
key: 'status',
header: 'Status',
render: (row: Row) => (
<span style={{ textTransform: 'uppercase' }}>{row.status}</span>
),
},
];
return <PixelTable<Row> columns={columns} data={rows} />;
}Clickable rows React Vue Angular
import * as React from 'react';
import { PixelTable } from '@pxlkit/ui-kit';
type Row = {
id: string;
name: string;
role: string;
status: string;
};
const rows: Row[] = [
{ id: '1', name: 'Alice', role: 'Engineer', status: 'active' },
{ id: '2', name: 'Bob', role: 'Designer', status: 'idle' },
{ id: '3', name: 'Carol', role: 'PM', status: 'active' },
];
const baseColumns = [
{ key: 'name', header: 'Name' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status' },
];
export function ClickableRows() {
const [last, setLast] = React.useState<string>('');
return (
<div>
<PixelTable<Row>
columns={baseColumns}
data={rows}
onRowClick={(row) => setLast(row.name)}
/>
<p>Last clicked: {last || 'none'}</p>
</div>
);
}