Skip to content

Table

Table shows rows you can sort, filter, select and act on. It has three forms that share one set of options: classic (a frame or a list), rich (typed columns and a stable key) and remote (a loader that returns one window of rows at a time). For a walk-through, see Your first dashboard; for patterns, see Dashboard recipes.

Table(source=None, *, rows=None, columns=None, key=None, ...)

Form Call Use it for
Classic Table(source) A pandas frame, a {"columns", "rows"} dict or a list of row dicts. Sorting and paging happen in the browser. selected holds the row index.
Rich Table(rows=..., columns=[Col(...)], key="id") Typed columns and a stable key per row. selected holds the key, so it survives sorting and new rows. rows may be a list, a signal or a Stream. columns may be left out when rows is a non-empty static list.
Remote Table(columns=[...], key="id", remote=loader) More rows than one prop should carry. The loader is asked for a window at a time.
Parameter Type Default Description
source frame, dict or list None Classic form data.
rows list, signal or Stream None Rich form data. A Stream sends new rows as small append patches; the browser keeps the newest stream.limit.
columns list[Col] None The columns.
key str None The field that identifies a row. Required for actions, checked, remote and keyboard use.
selected Signal None The clicked row: its key (rich) or its index (classic). None when nothing is selected.
page_size int 0 Rows per page; 0 shows all. In the remote form it is the window size (50 when unset).
label str "" The table's label, sent as its label prop.
height CSS length None Makes the body scroll under a sticky header. Needed by follow and virtual.
search bool False A filter box. Case-insensitive over every column and run in the browser, so streamed rows are filtered too. In the remote form the loader does it.
column_toggle bool False Lets the viewer hide columns, remembered in the browser.
newest_first bool False Shows rows in reverse arrival order (newest at the top, as a log reads) until a column is sorted. With follow, the top stays in view and following pauses when the viewer scrolls down. Rich form only.
follow bool False Keeps the newest row in view while rows arrive, and pauses when the viewer scrolls up. Needs height. Raises ValueError with remote.
actions list[RowAction] None A button per row. Needs key.
checked Signal[list] None A checkbox column plus select-all, holding the ticked keys. Needs key.
remote Callable[[TableQuery], (rows, total)] None The loader. A sync loader runs in a worker thread.
reload_on list[Signal] None Re-runs the last query when one of these changes.
query Signal[TableQuery \| None] None Mirrors the latest query.
virtual bool False Renders only the rows in view, for tens of thousands of rows. Needs height. Rows have one fixed height.
density str or Signal[str] None "compact" shrinks this table. A signal lets the viewer toggle it. The app-wide default is create_app(density=).
on_open Callable[[key], Any] None Called on Enter for the active row. Turns on keyboard use.
keyboard bool False Turns on keyboard use without on_open. Enter then selects, if selected is bound.

Keyboard. Focus the table with Tab or a click. j/k or the arrows move the active row, Home/End jump, PageUp/PageDown move a screenful, Space ticks a row of a checked table, and Escape or Tab leaves. Moving posts nothing; only Enter does.

Remote tables. The browser debounces search, sort and paging into a query. A newer query supersedes an older one, whose late answer is dropped, and an offset past the end is pulled back to the last window. With checked, the header checkbox ticks only the rows shown, and a "Select all M" button gathers every match, up to 10,000 keys.

Col(field, title=None, kind="text", ...)

Parameter Type Default Description
field str required The row field this column shows.
title str None The header; defaults to the field.
kind str "text" The cell renderer, below.
width CSS length None Column width, such as "9ch".
sortable bool True Whether a click on the header sorts.
tones dict[str, str] None For badge: maps a cell value to ok, warn, err, info or neutral.
time_format str "relative" For time: "relative" ("3s ago", refreshed live), "clock" (09:14:22.318) or "datetime" (2026-10-21 09:14:22), both in the viewer's local time. The tooltip always carries the full UTC value.
render spec None For cell: a render spec in the vocabulary of a List item.
sort_key str None For cell: the row field to sort and search by. Defaults to field.
truncate bool False Clips a long value with an ellipsis at max_width and shows the full text on hover.
max_width CSS length None The clip width for truncate (32ch when unset).
wrap bool False Lets a long value wrap instead. Refused with virtual=True.
kind Cell
text Plain text.
mono Monospace, truncated, with the full value on hover.
badge A pill toned by tones.
time An ISO string or epoch seconds or milliseconds, shown by time_format.
number Right-aligned, with grouped digits.
cell A composite cell drawn from the row's fields. Display only: an on in the spec is refused. The spec travels once, in the column.

No cell interprets HTML.

RowAction(label, on_click, confirm=None, variant="ghost", when=True)

A per-row button on a rich Table.

Parameter Type Default Description
label str or Callable[[row], str] required The button text. A function gets the row dict, so one column can say "Make editor" on one row and "Make viewer" on another.
on_click Callable[[key], Any] required Runs with the row's key. Sync or async.
confirm str None The shell asks this first. A UX guard, not authorization.
variant str "ghost" The button style.
when bool or Callable[[row], bool] True Where it is false, the row has no button.

A row can be stale by the time a click lands, so the handler must tolerate a key that no longer exists. when only hides the button: the click is still dispatched, so keep the real check in on_click or your authorization layer. A label or when function that raises hides the button on that row and does not fail the page. The resolved label and visibility are sent with each row, so a plain string label sends nothing extra.

TableQuery

What a remote table passes to its loader.

Field Type Meaning
search str The filter text.
filters dict A dict of filters that arrives with the query (empty unless the page sends some).
sort (field, "asc" \| "desc") or None The order.
offset int The first row wanted.
limit int How many rows.
seq int Numbers the viewer's queries so an old answer is never shown over a new one.

The loader returns (rows, total), where total is the count of rows matching the search. Sorting and search are the loader's job: a loader that ignores them is a bug in your app.