Elements
Table
A Table lays out a few rows the app writes as children (invoices, a summary, a comparison) and gives screen readers
a table: the table itself, groups of rows, rows, column headers and cells, each with its role. Its parts are
TableHeader, TableBody and TableFooter (groups of rows), TableRow (its cells side by side), TableHead (a
column’s header cell), TableCell and TableCaption. The look is the app’s: no part has colors, borders, paddings or
widths of its own, and each takes the app’s class and style. For many rows, sorting, selection and resizable
columns, DataTable is the table.
import { mount, Text, View } from "nsh";
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "nsh/base";
import { Stage, size, ui } from "./palette";
// A few rows the app writes out: the parts give screen readers a table, the
// classes and the palette give it its look.
const invoices = [
{ id: "INV001", status: "Paid", method: "Card", amount: 250 },
{ id: "INV002", status: "Pending", method: "Transfer", amount: 150 },
{ id: "INV003", status: "Unpaid", method: "Card", amount: 350 },
{ id: "INV004", status: "Paid", method: "Cash", amount: 450 },
];
const widths = ["w-24", "flex-1", "w-24", "w-24"];
function Cell(props: { i: number; head?: boolean; end?: boolean; children: string }) {
const style = { fontSize: size.label, color: props.head ? ui().muted : ui().text, fontWeight: props.head ? ("500" as const) : ("400" as const) };
const Part = props.head ? TableHead : TableCell;
return (
<Part class={`${widths[props.i]} px-3 py-2.5`}>
<Text style={{ ...style, textAlign: props.end ? "right" : "left" }}>{props.children}</Text>
</Part>
);
}
function App() {
const line = { borderBottomWidth: 1, borderColor: ui().line };
const total = invoices.reduce((sum, i) => sum + i.amount, 0);
return (
<Stage width={520}>
<View style={{ ...ui().card, borderRadius: 12, overflow: "hidden" }}>
<Table accessibilityLabel="Recent invoices">
<TableHeader>
<TableRow style={line}>
{["Invoice", "Status", "Method", "Amount"].map((title, i) => (
<Cell i={i} head end={i === 3}>
{title}
</Cell>
))}
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((inv) => (
<TableRow style={line}>
<Cell i={0}>{inv.id}</Cell>
<Cell i={1}>{inv.status}</Cell>
<Cell i={2}>{inv.method}</Cell>
<Cell i={3} end>{`$${inv.amount.toFixed(2)}`}</Cell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<Cell i={0} head>Total</Cell>
<Cell i={1}>{""}</Cell>
<Cell i={2}>{""}</Cell>
<Cell i={3} end>{`$${total.toFixed(2)}`}</Cell>
</TableRow>
</TableFooter>
<TableCaption class="px-3 py-2">
<Text style={{ fontSize: size.detail, color: ui().muted }}>A list of your recent invoices.</Text>
</TableCaption>
</Table>
</View>
</Stage>
);
}
mount(App);use nsh_base::{table, table_body, table_cell, table_head, table_header, table_row, theme};
use nsh_core::ui::prelude::*;
fn invoices() -> impl IntoElement {
let cell = |label: &'static str| table_cell().w(px(120.0)).px_3().py_2().child(text(label));
table()
.accessibility_label("Recent invoices")
.child(
table_header().child(
table_row()
.border_b_1()
.border_color(move || theme().border)
.child(table_head().w(px(120.0)).px_3().py_2().child(text("Invoice")))
.child(table_head().w(px(120.0)).px_3().py_2().child(text("Amount"))),
),
)
.child(table_body().child(table_row().child(cell("INV001")).child(cell("$250.00"))))
}import { mount, Text } from "nsh";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "nsh/base";
function App() {
return (
<Table accessibilityLabel="Recent invoices">
<TableHeader>
<TableRow class="border-b border-border">
<TableHead class="w-32 px-3 py-2"><Text>Invoice</Text></TableHead>
<TableHead class="w-32 px-3 py-2"><Text>Amount</Text></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell class="w-32 px-3 py-2"><Text>INV001</Text></TableCell>
<TableCell class="w-32 px-3 py-2"><Text>$250.00</Text></TableCell>
</TableRow>
</TableBody>
</Table>
);
}
mount(App);Table and its parts come from nsh/base, the nsh-base crate in Rust, where table(),
table_header(), table_body(), table_footer(), table_row(), table_head(), table_cell() and
table_caption() make them, with the typed style methods and .child(..). A row lays its cells out side by side; the
app gives cells their widths, so the columns line up.
#Props
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
accessibilityLabel | .accessibility_label(..) | string | none | Table only: what screen readers call the table. |
children | .child(..) | JSX.Element | none | What the part holds: groups and a caption in a Table, rows in a group, cells in a row, content in a cell. |
class, style | style methods | string, StyleProp<ViewStyle> | none | The part’s look and layout. |
#Parts
| Part | Inside | Role for screen readers |
|---|---|---|
Table | table | |
TableHeader | Table | row group: the rows of column headers |
TableBody | Table | row group: the rows of data |
TableFooter | Table | row group: totals, a summary row |
TableRow | a group, or Table | row; its cells side by side |
TableHead | a TableRow of the header | column header |
TableCell | TableRow | cell |
TableCaption | Table | content the app shows about the table |
Each platform gives the roles to its screen reader: table, row, cell and column header roles on Linux (AT-SPI), the web (ARIA), Android and the GPUI backend; grids and groups on macOS and Windows; headers as headers on iOS.
#See also
- DataTable: a table of any number of rows and columns, with selection, sorting and resizable, movable columns.
- Accessibility: the
accessibilityRolevalues,table,row,cell,columnheaderandrowgroupamong them.