Opinionated data grid with sort, selection, filters, and pagination.
Introduction
DataTable is an opinionated grid for columns and rows: sorting, selection, filters, search, empty and loading states, and pagination wiring. It composes Table for layout, sticky header, and column alignment. size, variant, full, striped, hoverable, and rounded are forwarded to Table.
When to use: Interactive datasets where the app owns fetch and DataTable owns chrome. Use Table for static markup you compose yourself.
Compared to similar components:
- Table — Presentational primitives (
table/thead/tbody). No sort, selection, or pager. - Pagination — Numbered page controls. DataTable renders its own chrome footer (selection summary, per-page Select, first / previous / next / last) instead of Pagination.
- List — A vertical index of items. Use List for navigation rows; use DataTable when values line up across columns and need grid chrome.
Use the framework selector in the site header to switch between React and Vue.
Install @tanstack/vue-table v9 next to @bridge-ui/vue. The public API stays columns / rows / sorting — the table engine is not exported.
Install @tanstack/react-table v9 next to @bridge-ui/react. The public API stays columns / rows / sorting — the table engine is not exported.
Import
import { DataTable } from "@bridge-ui/vue/Components/DataTable";import { DataTable } from "@bridge-ui/react/Components/DataTable";Basic usage
Pass columns and rows. Set sortable on a column to show a sort button in that header. Cell text defaults to row[id] (or accessor) when no renderer is set.
Sorting
Set sortable on a column to show a sort button in that header, matching the filter control. Clicks cycle unsorted → ascending → descending → unsorted. Bind sorting and onSortingChange (React) or v-model:sorting (Vue). The header cell keeps aria-sort; clicking the column title does not sort.
Client-side tables still sort the current rows when sorting is unbound. Bind it to control sort, or to send it with a server fetch when page and a total are set.
Variants
Chrome matches Table: plain (default), ghost, or bordered. The built-in footer pager is first / previous / next / last and does not follow table variant.
striped and hoverable are independent of variant. full={false} sizes columns to content; the bordered wrapper and chrome footer follow that width. stickyHeader pins header cells to the page; "boxed" pins them inside the wrapper — set a max height on classes.wrapper.
Pagination
The chrome footer shows a selection summary when selection is bound, a per-page Select when perPage is set (perPageOptions, default 10 / 25 / 50 / 100), and a first / previous / next / last pager when page is set with pageCount or totalCount (server fetch stays in the app). page + perPage without those totals slices rows locally.
When page and a total are set, DataTable does not sort or filter rows locally — bind sorting / filters / page and fetch the current page in the app. The pager also shows Page {{page}} of {{count}}. Replace the footer controls with slots.selected, slots.pagination, and slots.perPage.
Selection
Pass selection (always string[]) and getRowId. selectionMode="multiple" (default) uses checkboxes and select-all. selectionMode="single" uses radios and keeps at most one id. Binding selection also shows a chrome-footer summary ({{selected}} of {{total}} row(s) selected.).
Filters
Set filters on a column to show a funnel in that header. The panel uses checkboxes (filterMultiple, default) or radios (filterMultiple={false}), and Select all items for multiple filters. Nested children render as a group. OK commits; Reset clears the draft (commit on OK); closing without OK discards it.
Set searchable on a column to add a text field in that same overlay, and to include the column in the toolbar search. Queries live in columnSearch (column id → string) and filter rows — they do not filter the option list. Hidden columns skip their column-search queries.
Column filters open in a FieldOverlay. Default filterOverlay is auto (menu on desktop, drawer on mobile). Pass menu, modal, or drawer to pin a shell.
Search and toolbar
The toolbar search field is hidden by default. Pass showSearch to show it with internal state, or bind search when the app needs the query. Client-side tables filter columns with searchable; if none are set, every visible column is matched. Server-paged tables emit the query only.
slots.toolbar is the leading region. slots.toolbarActions sits in the end cluster beside Columns and Search. The toolbar shows when either slot is set, or when Columns / Search are enabled.
Sticky columns
sticky="start" or sticky="end" pins a column while the table scrolls horizontally. Selection and expand chrome pin to start when any data column uses sticky="start". Set width on pinned columns so offsets stay stable; pinned columns without a parseable width fall back to 120px. Keep those widths modest so other columns stay visible on small screens. Columns need enough width (or a constrained wrapper) so the table overflows and a horizontal scrollport appears.
Column classes
classes.header and classes.cell merge onto that column’s th / td (body and footer). Use width utilities for breakpoint-based sizing. Do not set width on the same column when the class should control width — inline width wins.
Column visibility
The toolbar Columns icon is hidden by default. Pass showColumnVisibility to show it with internal state, or bind hiddenColumns when the app needs the ids. hideable={false} keeps a column out of the toggle (or disabled). At least one column stays visible.
The panel opens in a FieldOverlay. Default columnsOverlay is auto (menu on desktop, drawer on mobile). Pass menu, modal, or drawer to pin a shell.
When unset, columnsShowFooter is true for modal / drawer (false for menu). Reset restores hideable columns; OK commits and closes. Closing without OK discards the draft.
Expand
Controlled expanded row ids. slots.expanded renders in a spanning row under the data row.
Empty, loading, and footer
Empty rows render EmptyState at the table size, with the semantic inbox icon and the i18n title "No data". slots.empty replaces it. loading keeps the table visible. loadingVariant="overlay" (default) dims the table with a spin; loadingVariant="bar" dims the table and draws a progress line under the header. slots.loading replaces the indicator. slots.footer renders below the table, above the chrome footer.
Item slots
cell on a column is for portable renderers. An item slot wins: React slots.item.{id}, Vue #item.{id}.
Summary
Set summary on a column to render a footer row over the current (filtered) rows. Chrome cells stay empty. Set ellipsis on a column to truncate overflowing cell text; the tooltip uses the column accessor. When any column sets ellipsis, the table switches to a fixed layout at full width, so give columns a width when they should not share space evenly.
Related components
Table, Pagination, List, Select, Menu, EmptyState
Accessibility
- DataTable keeps a real table with thead / tbody / optional tfoot.
- Sortable headers set aria-sort (
none/ascending/descending). - The sort control is a header button named "Sort ascending", "Sort descending", or "Cancel sorting".
- Selection checkboxes and radios are named Select row and Select all rows.
- Filter, Columns, Search, and Sort controls are named buttons/fields. Expand uses Expand row.
- loading sets aria-busy on the table.
Anatomy
<div> <!-- DataTable root -->
<div> <!-- toolbar: slots.toolbar, Columns, slots.toolbarActions, Search -->
<div> <!-- frame: hugs table when full is false -->
<div> <!-- table + loading overlay -->
<table> <!-- Table -->
<thead> <!-- header: label, sort button, filter; optional bar loading -->
<tbody> <!-- data rows, expand rows, empty state -->
<tfoot> <!-- summary -->
<div> <!-- slots.footer -->
<div> <!-- chrome footer: selection summary, per-page Select, page status, first / previous / next / last -->
</div>Use classes for root, toolbar, wrapper, table, header, body, row, head, cell, empty, loading, footer, search, selected, pagination, and perPage. Each column also accepts classes.header and classes.cell.
API
Bindings
| Prop / event | Type | Description |
|---|---|---|
| v-model:column-search | DataTableColumnSearch | Column id → text query. |
| v-model:expanded | string[] | Expanded row ids. |
| v-model:filters | DataTableFilters | Column id → selected option values. |
| v-model:hidden-columns | string[] | Hidden column ids. |
| v-model:page | number | 1-based page. |
| v-model:per-page | number | Page size. |
| v-model:search | string | Toolbar search query. |
| v-model:selection | string[] | Selected row ids. |
| v-model:sorting | DataTableSorting | Controlled sort, or null when unsorted. |
Bindings
| Prop | Type | Description |
|---|---|---|
| columnSearch | DataTableColumnSearch | Column id → text query. |
| expanded | string[] | Expanded row ids. |
| filters | DataTableFilters | Column id → selected option values. |
| hiddenColumns | string[] | Hidden column ids. |
| onColumnSearchChange | (search) => void | Called when column search changes. |
| onExpandedChange | (ids: string[]) => void | Called when expanded ids change. |
| onFiltersChange | (filters) => void | Called when filters change. |
| onHiddenColumnsChange | (ids: string[]) => void | Called when visibility changes. |
| onPageChange | (page: number) => void | Called when the page changes. |
| onPerPageChange | (perPage: number) => void | Called when page size changes (also resets to page 1). |
| onSearchChange | (query: string) => void | Called when toolbar search changes. |
| onSelectionChange | (ids: string[]) => void | Called when selection changes. |
| onSortingChange | (sorting) => void | Called when sort changes. |
| page | number | 1-based page. |
| perPage | number | Page size. |
| search | string | Toolbar search query. |
| selection | string[] | Selected row ids. |
| sorting | DataTableSorting | Controlled sort, or null when unsorted. |
DataTable
| Prop | Type | Default | Description |
|---|---|---|---|
| classes | DataTableClasses | — | Classes for root, toolbar, table wrapper, and descendant parts. |
| columns | DataTableColumn[] | [] | Column definitions. |
| columnsOverlay | FieldOverlayMode | "auto" | Overlay shell for the Columns panel: menu, modal, drawer, or auto. |
| columnsShowFooter | boolean | — | Reset / OK on the Columns overlay. Unset: true for modal / drawer, false for menu. Draft until OK. |
| customProps | DataTableCustomProps | — | Extra props for table parts, selection controls, chrome pager, per-page Select, search field, and progress. |
| filterOverlay | FieldOverlayMode | "auto" | Overlay shell for column filter panels: menu, modal, drawer, or auto. |
| full | boolean | true | Stretch the table to at least the wrapper width. When false, the wrapper hugs column content, and the chrome footer follows that width. |
| getRowId | (row: T) => string | — | Stable row id. Required for reliable selection and expand. |
| hoverable | boolean | false | Row hover styles on the body. |
| loading | boolean | false | Show the loading region (aria-busy on the table). |
| loadingVariant | "overlay" \| "bar" | "overlay" | Overlay spin, or a progress line under the header. Both dim the table. |
| pageCount | number | — | Total pages for the built-in chrome pager. Use this or totalCount. |
| perPageOptions | number[] | [10, 25, 50, 100] | Options for the built-in per-page Select. |
| rounded | TableRounded | "lg" | Corner radius of the wrapper, header, and footer. |
| rows | T[] | [] | Current page of data, or the full set when not server-paging. |
| selectionMode | "single" \| "multiple" | "multiple" | Radios or checkboxes. |
| showColumnVisibility | boolean | false | Show the toolbar Columns control with internal hidden-column state. Bind hiddenColumns when the app needs the ids. |
| showSearch | boolean | false | Show the toolbar search field with internal query state. Bind search when the app needs the query. |
| size | TableSize | "md" | Cell padding and type scale: sm, md, lg. |
| slots | DataTableSlots | — | empty, expanded, footer, item, loading, pagination, perPage, search, selected, toolbar, toolbarActions. |
| stickyHeader | boolean \| 'boxed' | false | Pin headers to the page, or inside the wrapper when "boxed". |
| striped | boolean | false | Alternating body row surfaces. |
| totalCount | number | — | Total item count; derives page count with perPage. pageCount wins if both are set. |
| variant | TableVariant | "plain" | Chrome forwarded to Table. The built-in footer pager does not follow table variant. |
Column
| Field | Type | Default | Description |
|---|---|---|---|
| accessor | (row: T) => unknown | row[id] | Value used for client-side sorting, filtering, search, and ellipsis tooltips. |
| align | TableAlign | "start" | Text alignment for the header and cells. |
| cell | (row: T) => ReactNode | accessor / row[id] | Portable cell renderer. Item slots win. |
| classes | DataTableColumnClasses | — | Extra classes for this column: header on the th, cell on body and footer td. |
| ellipsis | boolean | false | Truncate overflowing cell text and show the full value in a tooltip. |
| filterMultiple | boolean | true | When false, the filter overlay allows only one value. |
| filters | DataTableFilterOption[] | — | Filter overlay options. Presence enables the header funnel. |
| header | ReactNode | — | Header content. |
| hideable | boolean | true | When false, the column cannot be toggled in the Columns overlay. |
| id | string | — | Stable column id (also the default accessor key). |
| searchable | boolean | false | Adds a text field in that header’s filter overlay, and includes the column in the toolbar search. |
| sortable | boolean | — | Shows a sort button in the header. Clicks cycle unsorted → ascending → descending → unsorted. |
| sticky | "start" \| "end" | — | Pin the column while the table scrolls horizontally. |
| summary | (rows: T[]) => ReactNode | — | Footer cell for a summary row over the current (filtered) rows. |
| width | number \| string | — | Column width (px number or CSS length). Wins over class width when both are set. |