Table
Displays structured data in a row-and-column grid with sorting, row selection, multi-select, and a loading state. It can be wired together with a pagination component.
columns / data support a declarative attribute channel: pass a JSON string directly to render the header and data rows (invalid JSON falls back to the empty state), while the property channel (assigning arrays/objects, property takes precedence) remains available and can be serialized into an SSR snapshot.
Data channel contract (read alongside oas-chart): pass a JSON string via the attribute, or an object/array via the property (no serialization needed; parsed in the setter and takes precedence over the attribute).
oas-table'scolumns/datasetters reflect back to the attribute — after a property assignment,getAttribute('columns')/getAttribute('data')read back the corresponding JSON string (attribute and property stay in sync as a single source of truth). This differs fromoas-chart(itsdatawrites internal state only without reflecting; read it viael.data). Exception: whencolumnscontain function fields (such asrender), the setter takes a pure in-memory path without reflecting — read such columns via theel.columnsproperty.
Basic Usage (with Sorting)
Sortable columns
Click a sortable column header to cycle through ascending / descending / no sort.
Declarative child-element channel
Declare columns with <oas-table-column>
Nested child columns for a grouped header
Cell template cellTemplate (interpolate row.field)
Besides the columns attribute / property array, columns also support a declarative child-element channel: <oas-table-column data-key title sortable width align fixed ...>, with attributes aligned to the TableColumn fields (booleans are true/false, kebab-case like serial-number / filters; the column identifier field is key, but key is a reserved word in Vue templates and gets stripped before reaching the DOM — write data-key in declarative markup (plain HTML may use key directly; the component reads both)); title falls back to the default slot text; a nested <oas-table-column> expresses a grouped header (children). Child changes are picked up by a MutationObserver to auto re-render. An explicit columns attribute / property takes precedence over the child-element channel. The cellTemplate (with a row.field placeholder for interpolation) renders a custom cell template, cloned and hydrated per cell: plain HTML may declare it with a <template> inside the column; in Vue hosts / the docs site prefer the columns property channel (build an HTMLTemplateElement in JS, as shown in the example above — md/Vue compile pipelines handle <template> children inconsistently and empty them in dev); the render function still takes precedence over the template.
⚠️ Function fields (“a detail”): function types —
render,filterMatch, editor callbacks (functions ineditOptions) — cannot be serialized via a child-element attribute or JSON. Neither the child-element channel nor thecolumnsattribute can express them. For columns containing such function fields, assigncolumnsas a property (build the array in JS), or use the declarativecellTemplate(<template>+ arow.fieldplaceholder, a function-free alternative; see the example above) for custom cells.
Density Sizes
size: small / medium (default) / large
Each size only changes the default cell padding and font size, all via CSS variables: override with --oas-table-cell-padding-block / --oas-table-cell-padding-inline / --oas-table-font-size (takes precedence over the size preset). Invalid values fall back to medium with a warning. row-height is orthogonal to size: in fixed-row-height scenarios such as virtual scrolling, the row height is controlled by row-height and is not affected by the size preset.
Column Alignment and Width
Alignment and width
Controlled Sorting and Row Selection
Initial sort and selection
sort-key / sort-order control the sort; selected highlights the selected row (clicking a row toggles the selection).
Multi-Select
Row multi-select (checkable)
The header checkbox selects / clears all rows at once; row checkboxes toggle individually. Selection changes emit oas-check.
Row Single-Select (checkable="radio")
Row single-select (mutually exclusive radio)
Selected: none
checkable="radio" switches to single-select (a bare checkable keeps the multi-select behavior): the selection column renders radios with no header select-all checkbox (a blank header cell keeps the columns aligned), and clicking is mutually exclusive — selected holds single-value semantics (at most one row key). Clicking the already-selected row deselects it; oas-check carries the same { keys: string[] } detail (at most one element in single-select mode).
Row State Styling (row-class)
row-class: return a class per row
The disabled row is greyed out and the over-quota row is tinted warning — driven by the class returned from row-class.
row-class is a property function channel: (row, index) => string (multiple values separated by spaces). The returned classes land on the data row's tr and are also exposed as ::part() tokens on that row's tr and each of its cells — page CSS can then target them through the Shadow DOM with ::part() (the disabled/over-quota styling above uses exactly this mechanism). Functions cannot be serialized: it does not participate in the attribute / SSR snapshot (same contract as columns.render); in SSR scenarios assign it after client hydration.
Integration with Pagination
Table + pagination
Table data is sliced into 5 rows per page; on page change the data attribute is updated via the oas-change event and the table re-renders.
Fixed Columns
Left fixed column
Left/right fixed columns + sticky header
In the column config, fixed: 'left' | 'right' makes that column's header and cells position: sticky (the left / right offset is accumulated automatically from column widths); the remaining columns scroll horizontally, and the header always stays sticky.
Inline Editing
Double-click cells to edit (with operation column)
Last edit: —
Set editable to enable inline editing; mark editable columns with editable: true on the column config (editor: 'input' for text / editor: 'select' for a dropdown with editOptions), and actions: true renders an operation column (Edit / Save / Cancel buttons). Double-click a cell, or focus it and press Enter / F2, to start editing: Enter or blur submits, Esc cancels; submit emits oas-edit (detail: { rowIndex, key, column, value }), cancel emits oas-edit-cancel. Submitting an empty value restores the original (non-destructive by default).
Inline editing ships with the main entry: the main entry
@oas-ui/ui/data/tablealready bundles the editing capability (import registers it) — no explicit import needed (the demos below use the full entry, which also includes it). If you want a lean core without the editing machinery, import the pure-core entry@oas-ui/ui/data/table/coreinstead — it omits the capability, so<oas-table editable>silently no-ops with a dev hint; explicitlyimport '@oas-ui/ui/data/table/edit'(import registers it) or switch back to the main entry. (The editor is the heaviest part of the table chain — the pure core saves about one third; exact figures vary by version — see the repo'sdocs/perf-baseline.md.)
Editable cells come with perceptible affordances: hovering or focusing (focus-visible) a cell reveals a subtle background tint and a pencil icon at the top-right (the icon never intercepts interaction), and the cell carries a title hint ("Double-click to edit"). Three ways to start editing: double-click, or focus the cell and press Enter / F2.
Clicks / double-clicks landing inside an inline “interactive host” never cascade into row-level gestures: pointer events inside native controls (button / a / input / select / textarea), elements carrying a role attribute, or library interactive components (e.g. oas-button, oas-link, oas-select) only trigger the control itself — they neither toggle row selection nor get misread as a double-click-to-edit. Row clicks and double-click editing share the same exclusion list, kept in sync as new interactive components are added to the library. Business-specific interactive content inside a row (charts, mini widgets, etc.) needs no library release: add data-oas-row-click-ignore to its container to exempt that whole block from row clicks and row editing. Interactive hosts suppress row-level gestures but do not block the editing state itself.
Component Editor (editComponent)
editComponent: any component as the cell editor
editComponent (a columns JSON field) specifies a component editor (takes precedence over editor): double-clicking mounts the given component and injects the current value. Component contract (minimal): readable value (getFormValue() → value property → value attribute, three-level fallback — getFormValue is the internal form-associated channel of in-library components; host custom elements only need to implement either a value property or a value attribute), submit event (oas-change or native change, either one), Escape cancels; multiline editors (textarea-based) let Enter through for line breaks and submit on blur. In-library form components (oas-input / oas-textarea / oas-switch etc.) satisfy it natively, and host-defined custom elements work the same way. Note: horizontal scrolling that shifts the column window re-renders the whole table and silently cancels any in-progress edit (no oas-edit-cancel is dispatched) — overlay editors follow the same semantics.
Overlay Component Editors (oas-select / oas-date-picker)
Overlay editors: select + date picker
Overlay components (editComponent: 'oas-select' / 'oas-date-picker') automatically go through the overlay editor channel, which differs from the plain component channel:
- The overlay opens automatically on entering edit (driven through the component's uncontrolled open path via its trigger, without writing the controlled
openattribute); if the component is not registered (typo tag / missing import), it mounts as a blank element with no auto-open and no warning — consistent with the plain channel. - Pointer interaction inside the overlay panel does not misfire the blur-submit: the panel lives inside the component's shadow, so focus shifts caused by pointer presses within the editor subtree (picking an option, navigating the calendar, searching) are suppressed — single-value components commit on selection (
oas-change); multi-select components treat toggling as intermediate state and commit once on blur. - Two-level Escape: while the overlay is open, Escape only closes the panel (edit stays); pressing Escape again after the panel is closed cancels the edit.
- Click-outside commits: clicking outside both the editor and the overlay commits the current value; clicking another cell inside the table keeps the existing semantics (row re-render silently cancels the edit).
oas-select edit options sync from the column config: editOptions first, falling back to options (the same option semantics as the display side). The default editor of a type: 'multi-select' column is this channel's oas-select multiple (see "Field Types" below).
Controlled Editing
Controlled editing (edit-controlled)
This table is in controlled mode: the component does not write back data on submit; the host listens to oas-edit and updates it.
edit-controlled is the controlled editing mode: on submit the component does not write back data, it only emits oas-edit; the host listens and updates data itself (see the script below).
Sticky Rows
Sticky header + first N rows
sticky-rows="N" sticks the first N rows below the header (works with the scroll container: set height so the table body scrolls); it coexists with fixed columns (fixed: 'left' | 'right').
Large Data Sets (Virtual Scroll)
Virtual scroll with 10k rows
Setting height enables virtual scrolling (with a fixed row-height): only rows within the visible window are rendered. It works together with fixed columns, sorting, and multi-select; scrolling emits oas-scroll.
column-virtual: horizontal virtualization for wide tables
column-virtual enables horizontal virtualization (column windowing): only visible-window columns render; off-window columns collapse into colSpan placeholder cells (widths summed via colgroup; table-layout: fixed is enforced) — a 60-column table keeps only a dozen or so in the DOM. It can be combined with height row virtualization; fixed columns must use two-end layout and always render. Constraints (warns and degrades): grouped header / span-method / summary rows are incompatible; explicit width on every column is recommended (unset widths fall back to an estimated 100px). Note: horizontal scrolling that shifts the column window re-renders the whole table and silently cancels any in-progress edit.
Stripes and Borders
Striped rows (stripe)
Full border (bordered)
Set stripe to alternate the background of odd/even rows, or bordered to draw a full grid border around the cells.
Summary Row
Summary row (summary)
The summary attribute is a JSON array [{ key, type: 'sum' | 'avg' | 'count', label? }] rendered as a summary row at the table footer: label is shown in the first non-aggregated column, and each aggregated value in its corresponding column. You can also write summary: 'sum' | 'avg' | 'count' directly on a column config.
Summary scope
summary-scope controls the aggregation range of the summary row: all (default) totals the complete filtered result set before pagination slicing (stable across page flips), while page subtotals only the current page. The difference is visible when pagination is on.
Summary scope: all (default) vs page
In the first table above summary-scope is omitted (all): page 1 shows the total age sum of all 6 rows, 177. In the second summary-scope="page": page 1 subtotal is the current page's 3 rows (30+25+35=90), and the subtotal changes as you flip pages.
Expandable Rows
Expandable rows (expand field)
When a row's data has a non-empty expand field, an expand column appears at the end of the table; clicking the button expands the whole row to show custom content. The expanded state is stored in the expanded attribute (a comma-separated set of keys), and toggling emits oas-expand.
Controlled expansion (expanded attribute)
expanded is a controlled attribute (a comma-separated set of keys): pre-expanded rows open on the first render, and the host can add or remove keys at any time to drive the expansion state (shared by tree parent rows and expandable rows).
Tree Data
Tree data (children)
Rows with a children array are rendered as a tree: parent rows show an expand button in the first column, and child rows are indented by depth. The expanded state is stored in the same expanded attribute, and toggling emits oas-expand.
Loading State
Loading state
With the loading attribute, the header stays visible and the data area shows a placeholder row; removing the attribute restores the data.
Empty State
Empty data
Custom empty text
Rich empty content slot (slot="empty")
Empty state priority: the slot="empty" slot (either <template slot="empty"> or a plain element — a plain element is cloned as-is, a template clones its content) > the empty-text attribute > the built-in i18n text; adding or removing the slotted content re-renders automatically.
Column settings: show / hide / drag / resize
Column drag reorder + column width resize
Drag a header to reorder columns; drag the right edge of a header to resize. · Column order: original · Width: —
Column visibility (controlled column-keys / hidden)
Column drag reorder / resize fire oas-column-order / oas-column-resize (for host persistence). Two ways to hide a column: controlled column-keys (JSON array, also controls order) or hidden: true in the column config.
Touch adaptation: HTML5 drag-and-drop (dragstart) does not work on touch screens. Under coarse pointers each header cell shows move-up / move-down buttons (hidden on desktop) to swap a column with its neighbor; the header filter button, expand toggle and column-resize handle all grow to the 44px touch target; the filter panel positions itself away from viewport edges (flipping when space is short, never overflowing on narrow screens).
Multi-column sorting
Shift-click to multi-sort (multi-sort)
The initial `multi-sort='[{"key":"age","order":"asc"},{"key":"name","order":"asc"}]'` declares the multi-column sort state as JSON (header shows priority 1, 2…). Hold Shift and click sortable columns to accumulate / toggle / remove sorts, writing back to `multi-sort`; a plain click resets to single column.
Serial number & ellipsis
Serial number + ellipsis
A column with serialNumber: true renders the row number (starting from 1, not reading a data field); a column with ellipsis: true truncates overflowing content to a single line with an ellipsis (hover title shows the full text).
Grouped header
Grouped header (children)
children in a column config defines a group header: the group column spans its children (colspan) and leaf columns align with data rows via rowspan; data / sorting / visibility / drag all operate on leaf columns.
Built-in pagination
Table built-in pagination (pagination)
Setting `pagination` enables built-in pagination: top-level rows are globally sorted then sliced; page / page-size changes fire `oas-page-change`. Current page: 1
External data pagination (total)
Server-side pagination (total + controlled current, host swaps data)
Setting `total` declares the total row count: `data` holds only the current page slice (no internal slicing); page / page-size changes fire `oas-page-change` and the host fetches that page (this demo simulates server slicing with local data). Current page: 1 · rows in loaded page: —
Column filter
Column filter (filterable + filter-values controlled)
Merged cells
Merged cells (merge)
merge: true merges consecutive rows with the same displayed value in that column into a single rowspan cell (ignored in virtual-scroll mode).
Controlled Span (span-method)
span-method: explicit rowspan/colspan per cell
The quarter column is declared explicitly via spanMethod (Q1 / Q2 each span 2 rows); the sales column uses the column-level merge for same-value merging — the two mechanisms coexist independently per column.
span-method is a property function channel: (row, column, rowIndex, columnIndex) => [rowspan, colspan] | {rowspan, colspan}, declaring the merge explicitly per cell; returning 0 means the cell is covered and not rendered (the function declares the coverage). It coexists with the column-level merge independently per column (rows missing a cell due to an explicit span naturally break that column's merge grouping); it is ignored in virtual-scroll mode (the same fixed-height limitation as merge). rowIndex counts data rows (0-based, excluding expand content rows) and columnIndex follows the effective column order; functions cannot be serialized and do not participate in the attribute / SSR snapshot (same contract as columns.render).
Remote data & sort loading
Sort triggers a re-request (simulated remote)
Listen to oas-sort-change, then set loading and re-request remote paginated/sorted data; this demo shows the "sort → loading → re-render" handoff with a simulated delay. Server-side sorting is done by the host after it receives the event (the table only dispatches the event and shows the loading state).
Events
Sort and click events
Sort: none · Row: —
Header visibility
show-header toggles the table header (defaults to true, current behavior). Set it to "false" to render without the header row — column configuration still aligns the data cells. Useful for pure data listings or pages that already render their own title row above the table.
show-header=false (toggleable)
Table layout (table-layout)
table-layout="fixed" passes through to the inner table: column widths follow the declared width strictly (long content wraps/truncates instead of widening the column). By default nothing is passed through (the browser's auto baseline — columns adapt to content).
table-layout=fixed (strict column widths)
Row hover switch (hover)
hover="false" turns off the row hover background (default on). Useful when the row content carries its own interactive states and the full-row background change is unwanted. Purely visual — row click / selection behavior is unaffected.
Default hover vs hover=false
Default (hover on)
hover="false" (off)
Tree indent (indent-size)
indent-size controls the per-level indent of tree data in px (default 24). Use it for compact hierarchies or deeper trees.
indent-size=32 (wider per-level indent)
Row expand predicate (row-expandable)
The rowExpandable property (function channel) decides per row whether the tail expand button renders: returning false omits the button while the placeholder cell is kept so column alignment stays intact.
Only rows with a full profile are expandable
Wang Wu's profile is incomplete (rowExpandable returns false), so no expand button.
Capped height scrolling (max-height)
max-height caps the table body scroll container (numbers are px; CSS values like 50vh pass through): overflowing content scrolls inside while the sticky header pins to the top of the container. Use it for lightweight capping; use height for virtual scrolling (ten-thousand-row scale).
max-height=200 (sticky header)
Custom filter icon (filter-icon slot)
The filter trigger icon in a filterable column header can be customized via slot="filter-icon" (a <template> or a plain element); the default is the built-in filter glyph.
Custom filter icon via template
Cell & double-click events (oas-cell-click / oas-row-dblclick)
Clicking a data cell dispatches oas-cell-click (detail: row / column / value / rowIndex / columnIndex); double-clicking a data row dispatches oas-row-dblclick (detail: row / rowIndex). Clicks and double-clicks landing on interactive hosts inside the row (buttons, links, form controls) do not dispatch — the same exclusion list as row selection.
Cell click / row double-click feedback
Cell click: — · Row double-click: —
Export CSV / Excel (exportable)
exportable renders export buttons in a toolbar above the table (default: CSV; export-format="excel" switches to Excel, "csv,excel" shows both). The exported range is the header plus the currently visible data rows (after filtering / sorting / pagination slicing; virtual scrolling exports the full rendered set): actions columns are skipped, serialNumber columns export the row index, and select editors export the option label. CSV follows RFC 4180 escaping with a UTF-8 BOM; Excel uses SpreadsheetML (.xls). export-file-name sets the file name (default export). You can also call table.exportData('csv' | 'excel') programmatically — both the button and the method dispatch oas-export (detail { format, fileName, rowCount }).
Export CSV / Excel (click to really download)
Last export: —
Grid navigation (grid-navigation)
grid-navigation gives the table role="grid" semantics (header cells columnheader, rows row, cells gridcell) and a single Tab stop for the data-cell region: Tab focuses the scroll container, then the arrow keys roam the cells — Left/Right wrap across rows, Up/Down keep the column, Home/End jump to the row edges, Ctrl+Home/End to the grid's first/last cell, PageUp/PageDown page by the viewport (or by page-size when pagination is on). Enter/Space activate a control inside the focused cell (expand toggle, row checkbox), and editable cells hand the keyboard over to the editor while editing. Header controls (select-all, column filter) keep their own Tab stops.
Arrow-key roaming (click the table, then use the arrow keys)
Click a cell, then roam with the arrow keys · cell click:
Row drag sorting (row-draggable)
row-draggable adds a drag-handle column; dropping a handle onto another row dispatches oas-row-reorder with detail: { from, to, row } (from/to are indices in the currently visible data-row order; to is the insertion index after removing the dragged row, so the host can replay it with const [moved] = rows.splice(from, 1); rows.splice(to, 0, moved)). The component never mutates data itself (host-controlled, the same contract as sortable tabs / tree drag). Dragging is disabled under virtual scrolling (height) with a one-time dev warning.
Drag a handle to reorder (host replays oas-row-reorder)
Last reorder: —
Column field types (column type)
The type field in a column config declares the field type, driving the default cell rendering and the default editor (custom render / cellTemplate take precedence over type). All types: text (default plain text) / number / currency / select / multi-select / date / checkbox / link / progress / rate. Companion fields: currency (currency symbol, default ¥), options (select / multi-select options [{ value, label, color? }]; color falls back to the default token, unknown values render as raw text). Numeric columns (number / currency / progress / rate) sort by the raw numeric value when combined with sortable; date sorts chronologically. Badge / progress / star colors all use CSS variable tokens (readable in dark mode automatically).
Multiple field-type columns in one table
The example also covers empty states: row 3 has a non-numeric stock (raw text) and an ISO datetime (date part shown); row 6 has a null stock (empty), an invalid date (raw text), an out-of-range progress (clamped to 0%), and an out-of-range rating (clamped to 5 stars).
Inline editing per type: number / currency / link use a native input (numeric columns write back numbers); progress / rate clamp to 0-100 / 0-5 on submit; multi-select goes through the overlay component-editor channel mounting oas-select multiple (tick multiple options with options auto-synced as edit options; committing writes back a string array, and clearing every selection then blurring commits [] as a legitimate value; the first-release comma-separated input editor is retired — legacy comma-separated string data is parsed into the selection set when entering edit); date ships a built-in YYYY-MM-DD format check (invalid format keeps the editor open); checkbox mounts oas-switch through the component-editor channel (writes back a boolean); select reuses the existing select editor channel (options are auto-synced as edit options). Turn on editable and double-click any typed column to try it.
Grouped view (group-by)
group-by="fieldKey" renders sections by field value: a section header = collapse arrow + field value + row count; clicking the arrow collapses / expands that group (expanded by default, with aria-expanded kept in sync). Groups are ordered by first occurrence of the field value, and sorting applies within groups; rows with missing / empty grouping values fall into the "(empty)" group (i18n text). Section headers are a kind of flat row and participate in row virtualization, spanning the full width via colSpan (orthogonal to column-virtual / fixed columns); collapsed groups survive data / attribute changes. Combining with merge columns / span-method / row-draggable is unsupported (warns once and degrades to plain rendering). With pagination, grouping is per-page (group counts reflect the current page; cross-page groups reappear on each page).
Group by department (sortable within groups, click the arrow to collapse/expand)
Cell overflow tooltip (cell-tooltip)
cell-tooltip is on by default (set cell-tooltip="false" to disable): when a plain-text cell overflows (content wider than the column), hovering shows the full text in a floating layer — a table-level singleton tooltip (no per-cell instances), token inverse colors readable in dark mode, auto-hidden on scroll / re-render. ellipsis columns that already carry a native title and rich-content cells (custom render / badges / progress bars) do not trigger it, avoiding a double tooltip.
Hover an overflowing cell to read the full text
The full endpoint URLs far exceed the column width (long unbreakable strings): hover any overflowing cell to read the full text in the tooltip; it hides as soon as the pointer leaves.
API
oas-table
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
bordered | Full border: draws a grid outline around cells (the outer frame is built in) | — | — |
cell-tooltip | Cell overflow tooltip (on by default, "false" to disable): when a plain-text cell overflows (scrollWidth > clientWidth), hover shows the full text in a floating layer — one table-level singleton tooltip (token inverse colors, readable in dark mode), auto-hidden on scroll/re-render; rich-content cells and ellipsis columns with a native title are exempt (no double tooltip) | string | true |
checkable | Row selection: present = multi-select (checkboxes + select-all header); ="radio" = single-select (mutually exclusive, click again to deselect, no select-all), oas-check detail.keys ≤1 | string | — |
column-keys | Controlled column visibility and order (key array or comma list): header tree and data columns re-order to the effective leaf order (same-ancestor leaves grouped) | string[] | string | [] |
column-virtual | Column virtualization (column windowing): only visible-window columns render; off-window columns collapse into placeholder cells (colSpan-summed via colgroup). Constraints: explicit width on all columns recommended; grouped header / span-method / summary rows incompatible (warns and degrades) | boolean | — |
columns | Column config [{ key, title, sortable?, width?, align?, fixed?, render?, summary?, editable?, editor?, editOptions?, editComponent?, actions?, type?, currency?, options? }], JSON string (declarative attribute channel; property assignment takes precedence); type declares the field type (number/currency/select/multi-select/date/checkbox/link/progress/rate) driving default cell rendering and the default editor, currency is the currency symbol for currency columns (default ¥), options are select/multi-select options [{ value, label, color? }] | TableColumn[] | string | [] |
current | Current page (built-in pagination, controlled) | string | 1 |
data | Row data [{ [key]: value, children?, expand? }], JSON string (declarative attribute channel; property assignment takes precedence) | Array<Record<string, unknown>> | string | [] |
edit-controlled | Controlled editing: does not write back data on submit, only fires oas-edit; the host listens and updates data itself | boolean | — |
editable | Inline editing switch (requires editable: true on columns; same for the actions: true operation column) | boolean | — |
empty-text | Empty state text | — | — |
expanded | Set of expanded row keys (comma-separated; shared by tree parent rows and expandable rows) | string | — |
export-file-name | Export file name (without extension; path separators and reserved characters are sanitized) | string | export |
export-format | Export format: csv (default) / excel, or the comma list csv,excel (both buttons); invalid falls back to csv | string | csv |
exportable | Enables export: renders export buttons in a toolbar above the table (CSV by default, CSV/Excel configurable), and exposes the exportData method | boolean | — |
filter-values | Controlled column filter values (JSON object: column key → selected scalar value) | string | — |
grid-navigation | Keyboard grid navigation: role=grid, single Tab stop for the data region, arrow keys roam cells (Home/End/PageUp-Down, Enter/Space activate in-cell controls) | boolean | — |
group-by | Grouped view: render sections by field value (section header = value + row count + collapse arrow, click to collapse/expand, expanded by default); groups ordered by first occurrence, sorting applies within groups; empty values fall into the "(empty)" group; combined with merge columns / span-method / row-draggable it warns once and degrades to plain rendering; works with column-virtual / virtual scrolling (section header spans the full width) | string | — |
height | Virtual scroll viewport height (px); when set, only visible-window rows plus head/tail placeholders are rendered | string | 320 |
hover | Row hover background switch (purely visual), "false" disables | — | — |
indent-size | Per-level indent of tree data in px | string | 24 |
loading | Loading state: shows placeholder rows in the data area (header retained) | boolean | — |
max-height | Caps the body scroll area with a sticky header (virtual height wins when both set) | string | — |
multi-sort | Multi-column sort (JSON array: [{ key, order }], applied in array order) | string | — |
page-size | Rows per page (built-in pagination, default 10) | string | 10 |
pagination | Built-in pagination switch (footer pager; leave unset when the host paginates) | boolean | — |
row-draggable | Row drag sorting: renders a drag-handle column and dispatches oas-row-reorder on drop; disabled with a warning under virtual scrolling | boolean | — |
row-height | Fixed row height for virtual scrolling (px) | string | 40 |
row-key | Unique key field of a row | string | key |
selected | Set of selected row keys (comma-separated) | string | — |
show-header | Toggles the header (default true); with false no header row is rendered while column configuration still aligns data cells | string | true |
size | Density preset: small / medium (default) / large — only changes default cell padding and font size (all via CSS variables; override with --oas-table-cell-padding-block / --oas-table-cell-padding-inline / --oas-table-font-size, which take precedence); invalid values fall back to medium with a warning; orthogonal to row-height | string | medium |
sort-key | Controlled sort; sort-order is asc / desc / empty | string | — |
sort-order | Controlled sort; sort-order is asc / desc / empty | SortOrder | — |
sticky-rows | Number of sticky rows (N): the first N rows stick below the header (coexists with the scroll container and fixed columns) | string | — |
stripe | Zebra striping: alternating light background for odd/even rows | boolean | — |
summary | Summary config [{ key, type: 'sum'|'avg'|'count', label? }], JSON string | string | — |
summary-scope | Summary aggregation scope: all (default, full dataset) / page (current page) | string | all |
table-layout | Passes the table layout algorithm through (with fixed, column widths strictly follow width) | string | — |
total | Total row count for external (server-side) pagination: pairs with pagination/current/page-size — data holds only the current page slice (no internal slicing) and the total feeds the pager directly; page turns emit oas-page-change for the host to swap data; absent or invalid values fall back to internal pagination | string | — |
Events
| Event | Description |
|---|---|
oas-cell-click | Fired on data-cell click, detail: { row, column, value, rowIndex, columnIndex } (suppressed inside interactive controls) |
oas-check | Checkbox selection change, detail: { keys: string[] } |
oas-column-order | Fired after column drag reorder, detail: { keys } (new column order) |
oas-column-resize | Fired after a column width drag, detail: { key, width } |
oas-edit | Inline edit submitted (Enter / blur / operation column save), detail: { rowIndex, key, column, value }; in controlled mode the component does not write back data |
oas-edit-cancel | Inline edit cancelled (Esc / operation column cancel / empty submit restores), detail: { rowIndex, key, column, value } (value is the original value) |
oas-expand | Row expand/collapse (tree child rows or expandable content rows), detail: { key, expanded } |
oas-export | Export triggered (button or exportData), detail: { format, fileName, rowCount } |
oas-filter-change | Fired when a column filter value changes, detail: { filters } (filters maps a column key to its selected scalar value) |
oas-page-change | Fired when the built-in page changes, detail: { page, pageSize } |
oas-row-click | Row click (also toggles selection when not checkable), detail: { row, key } |
oas-row-dblclick | Fired on data-row double-click, detail: { row, rowIndex } |
oas-row-reorder | Row reorder via drag or Alt+Arrow; detail: { from, to, row } (to is the insertion index after removing the dragged row; the component never mutates data) |
oas-scroll | Virtual scroll event (rAF throttled), detail: { scrollTop, start, end } |
oas-sort-change | Sort change, detail: { key, order: 'asc' | 'desc' | '' } |
Slots
| Name | Description |
|---|---|
template[slot="empty"] | Rich empty-state content (takes precedence over empty-text and the default empty text) |
template[slot="filter-icon"] | Custom filter trigger icon in filterable column headers |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-table-cell-padding-block | var(--oas-space-3) |
--oas-table-cell-padding-inline | var(--oas-space-4) |
--oas-table-font-size | var(--oas-font-size-md) |
--oas-tooltip-bg | var(--oas-color-text-primary) |
--oas-tooltip-color | var(--oas-color-bg) |
oas-table-column
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
actions | Column actions (e.g. save/cancel buttons for row editing) | — | — |
align | Column content alignment (left/center/right) | — | — |
currency | Currency symbol for currency-type columns (default ¥) | — | — |
data-key | Column key (escape channel for the Vue reserved word key; plain key also works in native HTML — the component reads both) | — | — |
editable | Column editable (double-click a cell to edit) | — | — |
editor | Editor type/configuration (select/input etc.) | — | — |
ellipsis | Single-line truncation with ellipsis for overflowing content (full text on hover title) | — | — |
filterable | Column filterable (filter trigger rendered in the header) | — | — |
filters | Column filter options configuration | — | — |
fixed | Column fixed (left/right pinning) | — | — |
hidden | Column hidden initially (available in the column visibility panel) | — | — |
key | Column key field name (native HTML writes key directly; Vue templates use data-key) | — | — |
merge | Auto-merge adjacent same-value cells in the column | — | — |
options | Display options for select/multi-select columns, JSON [{ value, label, color? }] (color falls back to the default token; auto-synced as edit options when type=select) | — | — |
serial-number | Row serial number column (1-based, not from data fields) | — | — |
sortable | Column sortable (header click cycles asc/desc/none) | — | — |
summary | Column participates in the summary row (sum/avg/count) | — | — |
title | Column header title (defaults to the default slot text) | — | — |
type | Column field type (number/currency/select/multi-select/date/checkbox/link/progress/rate; text is the default plain text): drives default cell rendering and the default editor; render/cellTemplate take precedence | — | — |
width | Column width (px or CSS value; fixed columns should declare it explicitly) | — | — |
Slots
| Name | Description |
|---|---|
| default | Custom cell content (row.field placeholder interpolation inside the template, double-curly syntax) |
Note:
columns.renderis a function type and can only be assigned via the property from JS — it cannot be expressed as a JSON string. Forfixedcolumns it is recommended to declarewidthexplicitly (sticky offsets fall back to 100px when omitted). Summary can also be written directly on a column assummary: 'sum' | 'avg' | 'count';children(tree child rows) andexpand(expandable row content) are both row data fields.
The loading placeholder row is exposed as ::part(loading-row), the summary row as ::part(summary-row), and the expandable content row as ::part(expand-row); each can be styled independently.