Skip to Content

Table

The Table component provides you with data tables with sorting, pagination, and selection.

Live Examples

Live Example
☀️
Copy
View Code
import { Table, type TableColumn } from '@jarllyng/nostromo' export default function TableDefault() { const data = [ { id: '1', name: 'John Doe', email: 'john@example.com' }, { id: '2', name: 'Jane Smith', email: 'jane@example.com' }, { id: '3', name: 'Bob Johnson', email: 'bob@example.com' } ] const columns: TableColumn<(typeof data)[number]>[] = [...
Live Example
☀️
Copy
View Code
import { Table, type TableColumn } from '@jarllyng/nostromo' export default function TableStriped() { const data = [ { id: '1', name: 'John Doe', email: 'john@example.com' }, { id: '2', name: 'Jane Smith', email: 'jane@example.com' }, { id: '3', name: 'Bob Johnson', email: 'bob@example.com' } ] const columns: TableColumn<(typeof data)[number]>[] = [...
Live Example
☀️
Copy
View Code
import { Table, type TableColumn } from '@jarllyng/nostromo' export default function TableBordered() { const data = [ { id: '1', name: 'John Doe', email: 'john@example.com' }, { id: '2', name: 'Jane Smith', email: 'jane@example.com' }, { id: '3', name: 'Bob Johnson', email: 'bob@example.com' } ] const columns: TableColumn<(typeof data)[number]>[] = [...

Import

import { Table } from "@jarllyng/nostromo"; // or import { Table } from "@jarllyng/nostromo/table";

Usage

import { Table, type TableColumn } from "@jarllyng/nostromo"; // A type alias rather than an interface. `data` is typed `Record<string, // unknown>[]`, and an interface has no index signature, so TypeScript refuses // it where an equivalent alias is accepted. type User = { id: string; name: string; email: string; }; export default function Example() { const data: User[] = [ { id: "1", name: "John Doe", email: "john@example.com" }, { id: "2", name: "Jane Smith", email: "jane@example.com" }, ]; const columns: TableColumn<(typeof data)[number]>[] = [ { key: "name", title: "Name", dataIndex: "name" }, { key: "email", title: "Email", dataIndex: "email" }, ]; return <Table data={data} columns={columns} />; }

Advanced Examples

With Sorting

Live Example
☀️
Copy
View Code
import { Table, type TableColumn } from '@jarllyng/nostromo' type User = { id: string name: string email: string } export default function SortableTable() { const [sortColumn, setSortColumn] = React.useState<string>()...

With Pagination

Live Example
☀️
Copy
View Code
import { Table, type TableColumn } from '@jarllyng/nostromo' export default function PaginatedTable() { const [currentPage, setCurrentPage] = React.useState(1) const [pageSize, setPageSize] = React.useState(10) const data = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`...

Props

Table

PropTypeDefaultRequiredDescription
dataT[]-YesArray of data objects
columnsTableColumn<T>[]-YesColumn definitions
variant"default" | "striped" | "bordered" | "hover" | "elevated" | "interactive""default"NoVisual variant of the table
size"sm" | "md" | "lg""md"NoSize of table cells
loadingbooleanfalseNoWhether the table is loading
emptyTextstring"No data"NoText to show when no data
captionstringundefinedNoTable caption (for accessibility)
classNamestringundefinedNoAdditional CSS classes
rowKeykeyof T | ((record: T) => string | number)"id"NoUnique key for each row
onRowClick(record: T, index: number) => voidundefinedNoCallback when row is clicked
onSort(column: TableColumn<T>, direction: 'asc' | 'desc') => voidundefinedNoCallback when column is sorted
sortColumnstringundefinedNoCurrently sorted column (controlled)
sortDirection'asc' | 'desc'undefinedNoSort direction (controlled)
paginationPaginationPropsundefinedNoPagination configuration
selectionSelectionPropsundefinedNoRow selection configuration

TableColumn

PropTypeDefaultRequiredDescription
keystring-YesUnique column key
titlestring-YesColumn header text
dataIndexkeyof TundefinedNoProperty name in data object
render(value: unknown, record: T, index: number) => React.ReactNodeundefinedNoCustom render function
sortablebooleanfalseNoWhether column is sortable
widthstring | numberundefinedNoColumn width
align'left' | 'center' | 'right''left'NoText alignment
classNamestringundefinedNoAdditional CSS classes

PaginationProps

PropTypeDefaultRequiredDescription
currentnumber-YesCurrent page number
pageSizenumber-YesNumber of items per page
totalnumber-YesTotal number of items
onChange(page: number, pageSize: number) => void-YesCallback when page changes
showSizeChangerbooleanfalseNoWhether to show page size changer
showQuickJumperbooleanfalseNoWhether to show quick jumper

SelectionProps

PropTypeDefaultRequiredDescription
selectedRowKeys(string | number)[]-YesArray of selected row keys
onChange(selectedRowKeys: (string | number)[], selectedRows: T[]) => void-YesCallback when selection changes
getCheckboxProps(record: T) => { disabled?: boolean }undefinedNoFunction to get checkbox props for each row

Selection spans pages, the checkbox does not

selectedRowKeys is the whole selection. It can name records that are not on the page being rendered, and the Table never discards them.

That shapes two things worth knowing.

Select-all acts on this page. It adds every selectable row you can currently see to the selection, or removes them, and leaves every other key untouched. Its checked state is read the same way: all of the selectable rows on this page, some of them, or none. Keys belonging to other pages neither check it nor leave it indeterminate.

A disabled row is skipped, not selected for you. getCheckboxProps returning { disabled: true } takes the row out of select-all as well as out of its own checkbox. Once every row that can be selected is selected, select-all reads as checked rather than staying permanently indeterminate because of the rows that cannot be.

The keys are authoritative, the records are what is on screen

onChange gives you both:

onChange={(keys, rows) => { ... }}

keys is the complete selection. rows is only the subset present in data, because the component has never been given the rest. If you paginate server-side, keep your own key-to-record map and treat the keys as the source of truth.

Zero is a key

rowKey falls back to the row’s index only when the value is null or undefined. A record with id: 0, or an empty-string key, keeps its own key.

Last updated on