Table
A simple, stateless, composable table component for rendering tabular data. Unlike DataTable, this component does not include virtual scrolling, sorting, or column management — it is designed for straightforward data display using a declarative API.
#Import
use gpui_kit::component::table::{
Table, TableHeader, TableBody, TableFooter,
TableRow, TableHead, TableCell, TableCaption,
};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption } from "table.slint";use iced::widget::{column, container, mouse_area, row, svg, text};
use iced::{Alignment, Element, Length, Task, Theme};#Usage
#Basic table
Table::new()
.child(TableHeader::new().child(
TableRow::new()
.child(TableHead::new().child("Name"))
.child(TableHead::new().child("Email"))
.child(TableHead::new().text_right().child("Amount"))
))
.child(TableBody::new()
.child(TableRow::new()
.child(TableCell::new().child("John"))
.child(TableCell::new().child("john@example.com"))
.child(TableCell::new().text_right().child("$100.00")))
.child(TableRow::new()
.child(TableCell::new().child("Jane"))
.child(TableCell::new().child("jane@example.com"))
.child(TableCell::new().text_right().child("$200.00")))
)
.child(TableCaption::new().child("A list of recent invoices."))Table {
TableHeader {
TableRow {
TableHead {
text: "Invoice";
}
TableHead {
text: "Status";
}
TableHead {
text: "Method";
}
TableHead {
text: "Amount";
align: TextHorizontalAlignment.right;
}
}
}
TableBody {
for row[
i
]
in invoices: TableRow {
index: i;
TableCell {
text: row.invoice;
}
TableCell {
text: row.status;
}
TableCell {
text: row.method;
}
TableCell {
text: row.amount;
align: TextHorizontalAlignment.right;
}
}
}
TableCaption {
text: "A list of your recent invoices.";
}
}// The header band, twelve rows in a bounded scroll area, and the caption.
// Slint stacks the rows and lets the page scroll; this port puts the body on
// `scrollable`, so the header and the caption stay while the rows move. The
// `index` on each row is what draws the rule above it.
let mut basic_rows = Vec::new();
for (index, (invoice, status, method, amount)) in INVOICES.iter().enumerate() {
basic_rows.push(invoice_row(
index,
invoice,
status,
method,
amount,
ControlSize::Medium,
false,
));
}
let basic = Table::new()
.header(TableHeader::new().row(
TableRow::new()
.head("Invoice")
.head("Status")
.head("Method")
.head(TableHead::new("Amount").align(Align::End)),
))
.body(TableBody::new(basic_rows).height(Length::Fixed(BODY_HEIGHT)))
.caption("A list of your recent invoices.");#Striped, bordered and sizes
Table::new()
.child(TableHeader::new().child(
TableRow::new()
.child(TableHead::new().child("Invoice"))
.child(TableHead::new().child("Status"))
.child(TableHead::new().text_right().child("Amount"))
))
.child(TableBody::new()
.child(TableRow::new()
.child(TableCell::new().child("INV001"))
.child(TableCell::new().child("Paid"))
.child(TableCell::new().text_right().child("$250.00")))
)
.child(TableFooter::new().child(
TableRow::new()
.child(TableCell::new().child("Total"))
.child(TableCell::new().child(""))
.child(TableCell::new().text_right().child("$250.00"))
))GridLayout {
spacing: 8px;
Button {
horizontal-stretch: 0;
text: "Striped";
selected: striped;
clicked => {
striped = !striped;
}
}
Button {
horizontal-stretch: 0;
text: "Bordered";
selected: bordered;
clicked => {
bordered = !bordered;
}
}
Rectangle {
horizontal-stretch: 0;
width: 8px;
}
Button {
horizontal-stretch: 0;
text: "Small";
selected: table-size == ControlSize.small;
clicked => {
table-size = ControlSize.small;
}
}
Button {
horizontal-stretch: 0;
text: "Medium";
selected: table-size == ControlSize.medium;
clicked => {
table-size = ControlSize.medium;
}
}
Button {
horizontal-stretch: 0;
text: "Large";
selected: table-size == ControlSize.large;
clicked => {
table-size = ControlSize.large;
}
}
}
Table {
bordered: bordered;
TableHeader {
TableRow {
TableHead {
text: "Invoice";
size: table-size;
}
TableHead {
text: "Status";
size: table-size;
align: TextHorizontalAlignment.center;
}
TableHead {
text: "Method";
size: table-size;
}
TableHead {
text: "Amount";
size: table-size;
align: TextHorizontalAlignment.right;
}
}
}
TableBody {
for row[
i
]
in invoices: TableRow {
index: i;
striped: striped;
TableCell {
text: row.invoice;
size: table-size;
}
TableCell {
size: table-size;
align: TextHorizontalAlignment.center;
VerticalLayout {
alignment: center;
Rectangle {
property <color> tone: row.status == "Paid" ? Theme.success : row.status == "Pending" ? Theme.warning : Theme.danger;
height: 20px;
border-radius: Theme.radius-sm;
background: Theme.tint(tone);
HorizontalLayout {
padding-left: 6px;
padding-right: 6px;
Text {
text: row.status;
color: tone;
font-size: Theme.font-size-xs;
font-weight: 500;
vertical-alignment: center;
}
}
}
}
}
TableCell {
text: row.method;
size: table-size;
}
TableCell {
text: row.amount;
size: table-size;
align: TextHorizontalAlignment.right;
}
}
}
TableFooter {
TableRow {
TableCell {
text: "Total";
size: table-size;
col-span: 3;
}
TableCell {
text: "$1,750.00";
size: table-size;
align: TextHorizontalAlignment.right;
}
}
}
}// The three switches the Slint example puts above its table. Only what the
// caller changes is state: the table itself reads the switch back.
let controls = row![
Button::new("Striped")
.selected(app.striped)
.on_press(Message::ToggleStriped),
Button::new("Bordered")
.selected(app.bordered)
.on_press(Message::ToggleBordered),
Button::new("Small")
.selected(app.size == ControlSize::Small)
.on_press(Message::SetSize(ControlSize::Small)),
Button::new("Medium")
.selected(app.size == ControlSize::Medium)
.on_press(Message::SetSize(ControlSize::Medium)),
Button::new("Large")
.selected(app.size == ControlSize::Large)
.on_press(Message::SetSize(ControlSize::Large)),
]
.spacing(theme::SPACING_SM);
let mut striped_rows = Vec::new();
for (index, (invoice, status, method, amount)) in INVOICES.iter().take(5).enumerate() {
striped_rows.push(
invoice_row(
index,
invoice,
status,
method,
amount,
app.size,
true,
)
.striped(app.striped),
);
}
let striped = Table::new()
.bordered(app.bordered)
.header(TableHeader::new().row(
TableRow::new()
.head(TableHead::new("Invoice").size(app.size))
.head(TableHead::new("Status").size(app.size).align(Align::Center))
.head(TableHead::new("Method").size(app.size))
.head(TableHead::new("Amount").size(app.size).align(Align::End)),
))
.body(TableBody::new(striped_rows))
.footer(TableFooter::new().row(
TableRow::new()
.cell(TableCell::new("Total").span(3).size(app.size))
.cell(
TableCell::new("$1,750.00")
.align(Align::End)
.size(app.size),
),
));#Column widths
// A width on the head and the cell of a column pins it, and the columns
// without one share what is left. Slint expresses the same thing with
// `width` and leaves the rest at its preferred width.
let widths = Table::new()
.header(TableHeader::new().row(
TableRow::new()
.head(TableHead::new("ID").width(80.0))
.head("Name")
.head(TableHead::new("Date").width(120.0)),
))
.body(TableBody::new([
TableRow::new()
.index(0)
.cell(TableCell::new("INV-0042").mono(true).width(80.0))
.cell("Northwind Trading")
.cell(TableCell::new("2026-11-10").width(120.0)),
TableRow::new()
.index(1)
.cell(TableCell::new("INV-0041").mono(true).width(80.0))
.cell("Contoso Ltd")
.cell(TableCell::new("2026-11-09").width(120.0)),
]));Use .w() on TableHead and TableCell to set fixed column widths:
TableRow::new()
.child(TableHead::new().w(px(80.)).child("ID"))
.child(TableHead::new().child("Name")) // flex-1
.child(TableHead::new().w(px(120.)).child("Date"))#Text Alignment
// Center-aligned header
TableHead::new().text_center().child("Status")
// Right-aligned cell (e.g., for numbers)
TableCell::new().text_right().child("$1,000.00")// A center-aligned header and an end-aligned number, the two alignments a
// table needs beyond the default start.
let alignment = Table::new()
.header(TableHeader::new().row(
TableRow::new()
.head("Item")
.head(TableHead::new("Status").align(Align::Center))
.head(TableHead::new("Amount").align(Align::End)),
))
.body(TableBody::new([
TableRow::new()
.index(0)
.cell("Team plan")
.cell(TableCell::new("Active").align(Align::Center))
.cell(TableCell::new("$250.00").align(Align::End).mono(true)),
TableRow::new()
.index(1)
.cell("Seats")
.cell(TableCell::new("12").align(Align::Center))
.cell(TableCell::new("$60.00").align(Align::End).mono(true)),
]));#Without Border (via Styled)
// GPUI drops the edge with `.border_0().rounded_none()`. iced has no `Styled`
// trait, so the component carries the switch: `bordered(false)` keeps the
// fill and the bands and removes the edge and the corners.
let plain = Table::new()
.bordered(false)
.header(TableHeader::new().row(
TableRow::new()
.head("Method")
.head(TableHead::new("Amount").align(Align::End)),
))
.body(TableBody::new([
TableRow::new()
.index(0)
.cell("Credit Card")
.cell(TableCell::new("$250.00").align(Align::End).mono(true)),
TableRow::new()
.index(1)
.cell("PayPal")
.cell(TableCell::new("$150.00").align(Align::End).mono(true)),
]));All table sub-components implement the Styled trait, so you can customize styles directly:
// Remove border and rounded corners
Table::new()
.border_0()
.rounded_none()
.child(/* ... */)#Custom Styling
Since all components implement Styled, you can apply any GPUI style:
// Custom row hover
TableRow::new()
.bg(cx.theme().table_even)
.child(/* ... */)
// Custom cell padding
TableCell::new()
.px_4()
.child("Custom padded content")#Sub-components
| Component | Description |
|---|---|
Table | Root container with border, rounded corners, and background |
TableHeader | Header section with distinct background and font weight |
TableBody | Body section wrapping data rows |
TableFooter | Footer section with top border |
TableRow | A flex row with bottom border |
TableHead | Header cell with alignment and width options |
TableCell | Data cell with alignment and width options |
TableCaption | Caption text below the table |
#Table vs DataTable
| Feature | Table | DataTable |
|---|---|---|
| Virtual scrolling | No | Yes |
| Column sorting | No | Yes |
| Column resizing | No | Yes |
| Column moving | No | Yes |
| Cell selection | No | Yes |
| Row selection | No | Yes |
| Infinite loading | No | Yes |
| Keyboard navigation | No | Yes |
| State management | Stateless | TableState |
| Best for | Small, static data | Large, interactive datasets |
#API Reference
#Table
new()- Create a new table- Implements
Styled,ParentElement,Sizable,RenderOnce
#TableHead / TableCell
new()- Create a new head/cellw(width)- Set fixed width (otherwise flex-1)text_center()- Center-align contenttext_right()- Right-align content- Implements
Styled,ParentElement,RenderOnce
#TableHeader / TableBody / TableFooter / TableRow / TableCaption
new()- Create a new instance- Implements
Styled,ParentElement,RenderOnce
#Table
A static table. Mirrors GPUI Component's `Table`: a bordered, rounded surface that stacks a `TableHeader`, a `TableBody`, an optional `TableFooter` and a `TableCaption`. Rows are `TableRow`s of `TableHead` or `TableCell`; cells share the row width equally (100px minimum) unless given an explicit `width`, and align their content with `align`. Set `index` on body rows so they draw the row separator, and `striped` for alternating backgrounds. `bordered: false` removes the outer border and radius.
| Name | Kind | Type | Default |
|---|---|---|---|
bordered | in | bool | true |
#TableHeader
The header section of a `Table`: a muted band with a bottom separator.
#TableBody
The body section of a `Table`, wrapping data rows.
#TableFooter
The footer section of a `Table`: a muted band with a top separator.
#TableRow
A row of a `Table`. Rows after the first (`index > 0`) draw a top separator; with `striped`, odd rows get the even-row background.
| Name | Kind | Type | Default |
|---|---|---|---|
index | in | int | |
striped | in | bool |
#TableHead
A header cell. Shows `text` in the muted header style, or custom children.
| Name | Kind | Type | Default |
|---|---|---|---|
text | in | string | |
align | in | TextHorizontalAlignment | TextHorizontalAlignment.left |
size | in | ControlSize | ControlSize.medium |
col-span | in | int | 1 |
#TableCell
A data cell. Shows `text`, or custom children (badges, icons, buttons).
| Name | Kind | Type | Default |
|---|---|---|---|
text | in | string | |
align | in | TextHorizontalAlignment | TextHorizontalAlignment.left |
size | in | ControlSize | ControlSize.medium |
col-span | in | int | 1 |
text-color | in | color | Theme.foreground |
mono | in | bool |
#TableCaption
A caption below the table body, centered in the muted style.
| Name | Kind | Type | Default |
|---|---|---|---|
text | in | string | |
size | in | ControlSize | ControlSize.medium |