Tables
Which table to use
For a native HTML table that keeps its table layout at every width, use these primitives. For a table-like list that breaks down into cards on narrow screens, use ObjectsGrid. ViewList builds each model's list on ObjectsGrid.
This page shows the Visual Contract of the table primitives: Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption, TableEmpty, and TableRowActions. It ends with a DataTable recipe that composes them. Components describes the rules that every component page shares.
Table
Table renders a frame around a scroll container that holds the <table> element. The frame (Table.frame) draws the card fill, the card radius, and a Hairline edge. Rows and sticky header cells scroll inside the frame, so they never cover its edge. Wide tables scroll sideways inside it.
- Dividers: a hairline on each row's cells divides that row from the next row, and divides the header row from the body.
- Last row: the frame edge closes the table below the last body row.
- Footer:
TableFooterrows take a muted fill and muted text, with a divider above the first footer row. Use it for totals and summaries. - Caption:
TableCaptionrenders low-emphasis text below the rows. - Hover and selection: a row fills on hover. A row marked
data-state="selected"takes a primary tint and a leading primary rail, and hover and press deepen the tint. - Numbers and codes: a
TableHeadorTableCellmarkeddata-numericaligns right in a monospaced face. One markeddata-monouses the monospaced face at its normal alignment. The table sets tabular figures, so digits line up in every column. - Checkbox columns: a cell or header holding a checkbox tightens its trailing padding and aligns the box with the text line.
Theme keys: Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption. Current values: --card, --vueda-card-radius, --border, --vueda-hairline-width, --accent, --primary, --muted, and --muted-foreground.
Sticky header
With the Table.sticky prop, the container becomes a vertically scrolling region with a capped height. Header cells pin to the top and take the card fill, and each carries its divider over the scrolling rows. To change the cap, set the --vueda-tbl-max-h custom property on the Table or an ancestor.
Density
The Table.density prop sets a row height tier: default, compact, or condensed. Table writes it to data-density on the <table> element, and TableHead and TableCell read it. Each tier sets shorter rows than the one before it. Header cells shorten at compact and condensed, and condensed also uses smaller text. When density is unset, cells size to their padding and content.
Theme keys: TableHead.root and TableCell.root hold the tiers.
TableEmpty
TableEmpty is an empty-state row. It renders a TableRow with one TableCell that spans TableEmpty.colspan columns, and centers its content in a vertical stack. You supply the content in the default slot: an icon, a title, a description, and an action.
The TableEmpty.variant prop names the state: empty (the default), loading, error, or filtered. It sets data-variant on the content wrapper. A direct child marked data-slot="icon" takes the muted icon treatment, spins for loading, and takes the destructive color for error. For filtered, a ghost or tertiary action keeps the page's primary action the most prominent.
Theme key: TableEmpty. Current values: --muted-foreground and --destructive.
TableRowActions
TableRowActions groups small icon buttons inside a row. It appears while its row has the pointer or focus, or is selected. It holds its width while hidden, so the columns stay in place when it appears. Place it in a TableCell near the row's right edge. Its default slot receives TableRowActions.default.actionClass. Apply that class to each button, so row actions share one hover surface, border, and focus ring.
Theme key: TableRowActions. Current values: --muted, --border, and --ring.
DataTable recipe
This recipe composes a data table by hand from the table primitives. It adds a toolbar, a filter chip rail, a selection bar, and a pagination footer. Your application holds the sorting, selection, and paging state, for example with TanStack Vue Table.
- Toolbar and chips: search sits on the left and view controls on the right. Active filters show as removable chips below the toolbar.
- Sortable headers: each sortable
TableHeadsetsaria-sort. On adata-numericheader, the element markeddata-slot="sort-icon"moves before the label, so the column's right edge stays in place as the sort changes. - Selection: selected rows take the selected-row state. The selection bar uses the same primary tint, so it reads as part of the selection.
- Row actions: each row's
TableRowActionsshows for selected rows and on hover or focus for the others. - Height: the table uses
sticky, so the header stays visible while the rows scroll.