All components (204)
Table
Data · Table · .amset-table
What it is and when to use it
Data compared by columns (invoices, orders, customers), with sorting, per-column filters, selection, pagination, configurable columns and totals at the bottom.
When to use it
- Management lists where you search, sort and act on rows.
- It sorts, filters, selects and paginates on its own. The callbacks (
onSort,onFilter,onSelectionChange,onVisibleColumnsChange,pagination.onPageChange) only notify: each part is controlled by whoever uses it only if they also pass its value (sort,filters,selectedKeys,visibleColumns,pagination.page). These pages pass it all the callbacks and the table keeps working on its own. - With
serverSide(or withpagination.total) it does not sort or filter locally: it draws the rows as they arrive, and whoever uses it asks the server for what the notifications say. selection="multiple"withselectionActionsfor bulk actions;footerin a column to sum what remains after filtering.renderExpandedRowturns each row into an expandable one, with its detail at full width below;maxHeightfixes the height and keeps the header and the footer still.- Without pagination and inside a container with
data-density="compact"on a dashboard: density is global, not a prop.
When not to
- The data of a single thing, label and value:
DataList. - Thousands of rows that are scrolled through continuously, without columns:
VirtualList. - A comparison of plans or prices:
PricingTable. - A full spreadsheet (formulas, range selection, mass paste): the editing here is cell by cell.
Beyond the basics
groupBygroups by a column, with a collapsible header per group (initialCollapsedGroups,renderGroupLabel).advancedFiltersadds a panel of "column · operator · value" conditions joined by "and" or "or" (text, numbers, dates and yes/no, with "between" and "is empty");advancedFiltercontrols it from outside.exportCsvdownloads as CSV every row, already filtered and sorted, in the visible columns (with a BOM for Excel, the language's separator and text that looks like a formula neutralised).exportable: falseleaves a column out.persistKeyremembers visible columns, order and widths in the browser; the columns panel offers "Reset".editoron a column +onCellEditon the table: Enter, F2 or double click open the cell; Enter saves, Escape cancels, leaving saves. IfonCellEditfails, the field stays open with the error. The table does not changerows: whoever uses it updates their data.virtualized+maxHeightpaints only the rows that are visible, for thousands of rows (Home, End and the page keys jump even if the row is not painted). It does not combine withrenderExpandedRow.
Keyboard: the table is a single Tab stop. Up Arrow and Down Arrow go from row to row, keeping the column; Right Arrow enters the cells (to the control they have inside, such as the selection checkbox, which is checked with Space) and Left Arrow goes back to the row. Home and End go to the first and the last row. Pressing the header of a sortable column sorts (with the focus on it, also Enter or Space): ascending, descending and unsorted. The funnel in the header opens the filter (in the text one, Enter filters) and "Columns" opens a panel with checkboxes and move up and move down buttons, without dragging anything.
Accessibility: it is a real <table> with a grid role (role="grid"), a required name (label), headers with aria-sort and, with rowHeader, the column that names each row. With the filter applied, the button changes its name ("Filter for Customer (active)") and its shape, not just its color: the funnel fills in, just as the active sort turns off one of its two arrows. Both are identical 22 px boxes next to the title. The selection bar ("3 rows selected") and the page count go in role="status". While loading (loading) it carries aria-busy and, if there are no rows yet, says "Loading…".
Fine-tuning: CSS variables --amset-table-*: -cell, -column, -font, -padding-row, -radius, -row. The size changes the cell padding and the font size. The vertical padding is set by density (data-density) when there is one.
What it looks like
| Order | Customer | Status | Total |
|---|---|---|---|
| 1041 | Aitor Etxeberria | Shipped | €1,249.00 |
| 1039 | Javier Ortega | Shipped | €420.00 |
| 1043 | Pablo Gil | Delivered | €315.50 |
| 1038 | Lucía Martín | Delivered | €189.00 |
| 1042 | Nerea Sánchez | Pending | €89.00 |
<Table
label="October orders"
columns={[
{ key: 'id', title: 'Order', rowHeader: true },
{ key: 'cliente', title: 'Customer', sortable: true, filterable: 'text' },
{ key: 'estado', title: 'Status', filterable: 'list' },
{ key: 'total', title: 'Total', numeric: true, sortable: true },
]}
rows={pedidos}
rowKey={(pedido) => pedido.id}
initialSort={{ column: 'total', direction: 'desc' }}
pagination={{ size: 5, sizes: [5, 10] }}
/>Props
Import it with import { Table } from '@amset/ui'. Required props are marked.
| Prop | Type | What it does |
|---|---|---|
columnsrequired | readonly TableColumn<T>[] | — |
labelrequired | string | What is announced on entering the table. Required: without this it is just "table". |
rowKeyrequired | (row: T) => string | — |
rowsrequired | readonly T[] | — |
advancedFilter | AdvancedFilter | The advanced filter, controlled from outside. Without it, the table keeps it. |
advancedFilters | boolean | Adds the panel of filters with several conditions joined by "and" / "or". |
className | string | Merged with the library classes; it does not replace them. |
configurableColumns | boolean | Adds the panel to show, hide and reorder columns. |
emptyMessage | string | — |
expandedKeys | readonly string[] | Expanded rows (by their key), controlled from outside. Without them, the table keeps track of them. |
exportCsv | boolean | TableExportOptions | Adds a button that downloads as CSV what shows in the visible columns: every row, already filtered and sorted, not just the page. With `serverSide` it only exports the loaded rows. |
filters | Readonly<Record<string, string | readonly string[]>> | Filters controlled from outside. Without them, the table keeps them. |
groupBy | string | Groups the rows by the value of this column, with a collapsible header row per group. |
initialCollapsedGroups | readonly string[] | Groups collapsed at the start (by the value of their key). |
initialExpandedKeys | readonly string[] | The rows expanded at the start, without controlling them from outside. |
initialSort | TableSort | null | — |
loading | boolean | — |
maxHeight | string | Maximum height of the table (`'24rem'`, `'60vh'`): beyond it, the body scrolls and the header and footer stay fixed. It is the `--amset-table-height` variable made a prop. |
onAdvancedFilterChange | (filter: AdvancedFilter) => void | Notifies of the new advanced filter. It only notifies: it controls nothing. |
onCellEdit | (edit: CellEdit<T>) => void | Promise<void> | Saves what was edited in a cell with `editor`. It opens with Enter, F2 or double click and closes with Enter (saves), Escape (cancels) or leaving the field (saves). It can be asynchronous: if it throws, the field stays open with the error. The table does NOT change `rows`: whoever uses it updates their data. |
onExpandedChange | (keys: readonly string[]) => void | Reports with the expanded keys. It only reports: it does not control or turn anything off. |
onFilter | (filters: TableFilters) => void | Notifies of the new filters. It only notifies: it does not control or turn anything off. |
onRowPress | (row: T) => void | Notifies of the row pressed. Expanding the detail does not count. |
onSelectionChange | (keys: readonly string[]) => void | Notifies with the selected keys. It only notifies: it controls nothing. |
onSort | (sort: TableSort | null) => void | Announces the new order, or `null` when it is removed. It only announces: it does not control or turn anything off. |
onVisibleColumnsChange | (keys: readonly string[]) => void | Reports the visible columns, in their order. It only reports: it controls nothing. |
pagination | false | TablePagination | — |
persistKey | string | Remembers between visits which columns are shown, in what order and at what width, in the browser, under this key. With a controlled `visibleColumns` it does not remember the visible ones. |
renderExpandedRow | (row: T) => ReactNode | Turns every row into an expandable one and paints its detail here: an order with its lines, a customer with their history. Each row gains a button with `aria-expanded` at the start and the detail appears in a row below, full width. Rows with nothing to expand are marked with `rowExpandable`. |
renderGroupLabel | (value: CellValue, rows: readonly T[]) => ReactNode | What the header of a group says. By default, the value and the count. |
resizableColumns | boolean | The edge of each header can be dragged to change the width of the column. |
rowClassName | (row: T, index: number) => string | undefined | A class to mark a specific row (an overdue invoice in red) without touching the global CSS. It is ADDED to those of the table. `index` is the position in the page being shown. |
rowExpandable | (row: T) => boolean | Which rows can be expanded. All by default. |
selectedKeys | readonly string[] | Selected keys, controlled from outside. Without them, the table keeps them. |
selection | SelectionMode | — |
selectionActions | (keys: readonly string[]) => ReactNode | Bar that appears when rows are selected. |
serverSide | boolean | Sorting and filtering are done by the server: the table paints `rows` as they arrive and only notifies through `onSort` and `onFilter`. With `pagination.total` it is so without asking, because sorting a single page locally makes no sense. |
size | "sm" | "md" | "lg" | The size of the table: it moves the cell padding and the body font size. Not to be confused with `pagination.size`, which is the rows per page. |
sort | TableSort | null | Sort order controlled from outside. Without it, the table manages it, starting at `initialSort`. |
toolbar | ReactNode | Custom content in the top bar (search field, export button...). |
virtualized | boolean | TableVirtualization | Paints only the rows that are visible, for thousands of rows. It needs `maxHeight` (without a height there is no scrolling) and does not combine with `renderExpandedRow`, whose rows have different heights. |
visibleColumns | readonly string[] | Visible columns and their order, controlled from outside. Without them, the table keeps track of them. |
It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.
CSS variables
These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.
| Variable | Default |
|---|---|
--amset-table-row | var(--amset-space-2) |
--amset-table-cell | var(--amset-space-3) |
--amset-table-font | var(--amset-text-base) |
--amset-table-column | var(--amset-text-sm) |
--amset-table-radius | var(--amset-radius-lg) |
How to change it
.amset-table {
--amset-table-row: ...;
--amset-table-cell: ...;
}In the test bench
Every state and variant, to try one at a time, on its page at ui.amset.tech:
