Table
A static table whose columns carry the geometry every row obeys, and whose cells can hold a widget rather than only a string.
import { Table } from "@glint/components/table.slint";import { TableAlign, TableCellKind } from "@glint/components/table-cell.slint";import { BadgeVariant } from "@glint/components/badge.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 640px; height: 340px; background: Tokens.color-background;
VerticalLayout { padding: 24px; alignment: start;
Table { accessible-label: "Recent invoices"; caption: "A list of your recent invoices."; columns: [ { title: "Invoice", width: 110px }, { title: "Status" }, { title: "Method" }, { title: "Amount", align: TableAlign.end }, ]; rows: [ { cells: [ { text: "INV001" }, { kind: TableCellKind.badge, text: "Paid" }, { text: "Credit Card" }, { text: "$250.00" }, ] }, { cells: [ { text: "INV002" }, { kind: TableCellKind.badge, text: "Pending", badge-variant: BadgeVariant.secondary }, { text: "PayPal" }, { text: "$150.00" }, ] }, { cells: [ { text: "INV003" }, { kind: TableCellKind.badge, text: "Unpaid", badge-variant: BadgeVariant.destructive }, { text: "Bank Transfer" }, { text: "$350.00" }, ] }, ]; footer: [{ text: "Total" }, { }, { }, { text: "$750.00" }]; } }}Usage
import { Table } from "@glint/components/table.slint";import { TableAlign } from "@glint/components/table-cell.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component AppWindow inherits Window { width: 520px; height: 240px; background: Tokens.color-background;
VerticalLayout { padding: 24px; alignment: start;
Table { accessible-label: "Team seats"; columns: [ { title: "Member" }, { title: "Seats", align: TableAlign.end, width: 80px }, ]; rows: [ { cells: [{ text: "Engineering" }, { text: "24" }] }, { cells: [{ text: "Design" }, { text: "6" }] }, ]; } }}columns carries the headings and the geometry every row obeys; each row’s
cells line up with them by position. Naming the table is the call site’s job —
accessible-label is what a screen reader announces before the rows — and
caption is a descriptive title drawn under the table and announced as its
description.
TableRow describes this component’s rows. TableColumn, TableCell,
TableAlign and TableCellKind are shared with
DataTable, while DataTable uses its own
DataTableRow because its rows also carry selection state:
| Type | Fields |
|---|---|
TableColumn | title, width (leave it out to share the free space), align, hidden |
TableRow | cells, label (what a screen reader calls the row; falls back to the first cell) |
TableCell | kind, text, icon, badge-variant, checked, actions |
TableCellKind is what a cell draws: text (the default), badge, icon,
checkbox and actions. Every kind reads text — it is the value a text cell
shows, the label a badge carries, the name an icon is announced by, the label
beside a checkbox and the name of an action menu’s button. A kind ignores the
fields it has no use for (ADR-0026).
Rows carry no pointer handling of their own and no hover tint, on purpose: a row that lights up under the pointer is promising an interaction this table does not have. Reach for DataTable when the rows themselves must sort, paginate or act.
Examples
Column widths and alignment
A column with a width keeps it; one without shares the free space equally with
the other unsized columns. align decides where the cells sit inside that
width — numbers read right, labels read left, and a lone control reads centered.
import { Table } from "@glint/components/table.slint";import { TableAlign, TableCellKind } from "@glint/components/table-cell.slint";import { Tokens } from "@glint/theme/tokens.slint";import { IconSet } from "@lucide";
export component Demo inherits Window { width: 640px; height: 280px; background: Tokens.color-background;
VerticalLayout { padding: 24px; alignment: start;
Table { accessible-label: "Storage by region"; columns: [ { title: "Region", width: 160px }, { title: "Tier" }, { title: "Health", align: TableAlign.center, width: 90px }, { title: "Used", align: TableAlign.end, width: 110px }, ]; rows: [ { cells: [ { text: "eu-central-1" }, { text: "Standard" }, { kind: TableCellKind.icon, text: "Healthy", icon: IconSet.CircleCheck }, { text: "1.2 TB" }, ] }, { cells: [ { text: "us-east-1" }, { text: "Infrequent access" }, { kind: TableCellKind.icon, text: "Degraded", icon: IconSet.TriangleAlert }, { text: "840 GB" }, ] }, ]; } }}Widget cells
A cell is a TableCell rather than a string, so a status badge, an icon, a
checkbox or a per-row action menu can stand in one (ADR-0028). A checkbox cell
settles its own state in rows before cell-toggled fires, and an action cell
reports through cell-action with the entry that was taken.
import { Table } from "@glint/components/table.slint";import { TableAlign, TableCellKind } from "@glint/components/table-cell.slint";import { BadgeVariant } from "@glint/components/badge.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 640px; height: 300px; background: Tokens.color-background;
property <string> last: "Nothing taken yet.";
VerticalLayout { padding: 24px; spacing: 12px; alignment: start;
Table { accessible-label: "Release checklist"; columns: [ { title: "Done", align: TableAlign.center, width: 70px }, { title: "Task" }, { title: "State", width: 110px }, { title: "", align: TableAlign.end, width: 60px }, ]; rows: [ { label: "Tag the release", cells: [ { kind: TableCellKind.checkbox, checked: true }, { text: "Tag the release" }, { kind: TableCellKind.badge, text: "Shipped" }, { kind: TableCellKind.actions, actions: [ { label: "Re-run" }, { label: "Copy link" }, ] }, ] }, { label: "Publish the notes", cells: [ { kind: TableCellKind.checkbox }, { text: "Publish the notes" }, { kind: TableCellKind.badge, text: "Blocked", badge-variant: BadgeVariant.destructive }, { kind: TableCellKind.actions, actions: [ { label: "Re-run" }, { label: "Copy link" }, ] }, ] }, ]; cell-toggled(row, column, on) => { root.last = "Row " + row + " " + (on ? "ticked" : "cleared") + "."; } cell-action(row, column, entry, child) => { root.last = "Action " + entry + " on row " + row + "."; } }
Text { text: root.last; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; } }}A totals line
footer draws a row of cells under the body, in the body’s own columns. A
footer shorter than the columns leaves the rest blank rather than reading past
its own end, so { } is how a column is skipped. Its cells report as row -1.
import { Table } from "@glint/components/table.slint";import { TableAlign } from "@glint/components/table-cell.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 600px; height: 300px; background: Tokens.color-background;
VerticalLayout { padding: 24px; alignment: start;
Table { accessible-label: "Quarterly spend"; columns: [ { title: "Quarter", width: 120px }, { title: "Team" }, { title: "Spend", align: TableAlign.end, width: 120px }, ]; rows: [ { cells: [{ text: "Q1" }, { text: "Platform" }, { text: "$12,400" }] }, { cells: [{ text: "Q2" }, { text: "Platform" }, { text: "$9,850" }] }, { cells: [{ text: "Q3" }, { text: "Platform" }, { text: "$14,120" }] }, ]; footer: [{ text: "Total" }, { }, { text: "$36,370" }]; } }}A hidden column
hidden keeps a column off screen without reshaping a single row: the cells
stay where they are and the column they belong to collapses to nothing. Nothing
is left in the accessibility tree either — a hidden column draws no node at all.
import { Table } from "@glint/components/table.slint";import { Switch } from "@glint/components/switch.slint";import { TableAlign } from "@glint/components/table-cell.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 620px; height: 300px; background: Tokens.color-background;
in-out property <bool> show-owner: false;
VerticalLayout { padding: 24px; spacing: 16px; alignment: start;
Switch { label: "Show the Owner column"; checked <=> root.show-owner; }
Table { accessible-label: "Open incidents"; columns: [ { title: "Incident", width: 110px }, { title: "Summary" }, { title: "Owner", hidden: !root.show-owner, width: 130px }, { title: "Age", align: TableAlign.end, width: 80px }, ]; rows: [ { cells: [ { text: "INC-41" }, { text: "Elevated 5xx on checkout" }, { text: "Ada Lovelace" }, { text: "2h" }, ] }, { cells: [ { text: "INC-42" }, { text: "Search index lagging" }, { text: "Grace Hopper" }, { text: "6h" }, ] }, ]; } }}API Reference
Properties
| Property | Type | Default | Description |
|---|---|---|---|
columns | in [TableColumn] | no default | Columns, left-to-right: their headings, widths and alignment. |
rows | in-out [TableRow] | no default | Row data; each row's cells line up with columns by position. in-out because a checkbox cell settles its own state here before cell-toggled fires — the model is the state, as it is for a menu row. |
footer | in-out [TableCell] | no default | Totals line under the body, in the body's own columns. Empty leaves it off. Its cells report as row -1. |
caption | in string | no default | Descriptive title for the table, drawn under it and announced as its description. |
actions-label | in string | @tr("Row actions") | Name an action cell's button falls back to when its cell carries no text of its own. |
Callbacks
| Callback | Description |
|---|---|
cell-toggled(int, int, bool) | A checkbox cell was flipped: its row (-1 in the footer), its column and the state it now holds. |
cell-action(int, int, int, int) | An action cell fired: its row (-1 in the footer), its column, the entry's index and the submenu leaf's index, or -1 when the entry itself was taken. |
TableRow, TableColumn, TableCell and the TableAlign / TableCellKind
enums are data types rather than components; their fields are in
Usage above, and the primitives every table draws them with are on
DataTable’s page.
Accessibility
- Table role. The root carries
accessible-role: tableand publishesaccessible-item-countas the number of rows. Naming it is the call site’s — setaccessible-label.captionbecomes the table’saccessible-description, and the caption text under the table is silenced as a node of its own so a screen reader hears it once. - Rows. Slint 1.17 has a
tablerole but no row, cell or column-header member, so each row wearslist-itemwith its index. A row is announced by itslabel, which falls back to the first cell — the column a table is usually read by. - Cells. A cell that draws no control is a text node announced as its column then its value (“Status: Paid”), carrying the column’s position so assistive technology can navigate across a row as well as down a column. That pairing is as close to header association as the platform gets.
- Widget cells speak for themselves. A checkbox cell announces itself a
checkbox and an action cell a button, each carrying the column’s position the
same way. A box with nothing written beside it is named by its column;
actions-labelnames an action button whose cell carries no text. - Hidden columns leave no trace. A hidden column draws nothing at all rather than a collapsed node assistive technology would still find.
- No row affordance. Rows take no pointer events and draw no hover tint, so nothing invites a click the table does not answer. Only a widget cell takes the pointer, and it takes it the way any control does.