Skip to content

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's columns / data setters 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 from oas-chart (its data writes internal state only without reflecting; read it via el.data). Exception: when columns contain function fields (such as render), the setter takes a pure in-memory path without reflecting — read such columns via the el.columns property.

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 in editOptions) — cannot be serialized via a child-element attribute or JSON. Neither the child-element channel nor the columns attribute can express them. For columns containing such function fields, assign columns as a property (build the array in JS), or use the declarative cellTemplate (<template> + a row.field placeholder, 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/table already 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/core instead — it omits the capability, so <oas-table editable> silently no-ops with a dev hint; explicitly import '@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's docs/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 open attribute); 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

Simulate 2s loading

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")

No records yet — click the button to add oneAdd data

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)

Click the filter icon in a header to open the option popup; selecting a value filters rows and fires `oas-filter-change`. `filter-values` is the controlled attribute (JSON `{column: value}`; removing or emptying it clears the filter). Current filter: noneClear filter

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)

Clear sort & re-request

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)

Hide header

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 ​

AttributeDescriptionTypeDefault
borderedFull border: draws a grid outline around cells (the outer frame is built in)——
cell-tooltipCell 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)stringtrue
checkableRow selection: present = multi-select (checkboxes + select-all header); ="radio" = single-select (mutually exclusive, click again to deselect, no select-all), oas-check detail.keys ≤1string—
column-keysControlled 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-virtualColumn 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—
columnsColumn 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[]
currentCurrent page (built-in pagination, controlled)string1
dataRow data [{ [key]: value, children?, expand? }], JSON string (declarative attribute channel; property assignment takes precedence)Array<Record<string, unknown>> | string[]
edit-controlledControlled editing: does not write back data on submit, only fires oas-edit; the host listens and updates data itselfboolean—
editableInline editing switch (requires editable: true on columns; same for the actions: true operation column)boolean—
empty-textEmpty state text——
expandedSet of expanded row keys (comma-separated; shared by tree parent rows and expandable rows)string—
export-file-nameExport file name (without extension; path separators and reserved characters are sanitized)stringexport
export-formatExport format: csv (default) / excel, or the comma list csv,excel (both buttons); invalid falls back to csvstringcsv
exportableEnables export: renders export buttons in a toolbar above the table (CSV by default, CSV/Excel configurable), and exposes the exportData methodboolean—
filter-valuesControlled column filter values (JSON object: column key → selected scalar value)string—
grid-navigationKeyboard 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-byGrouped 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—
heightVirtual scroll viewport height (px); when set, only visible-window rows plus head/tail placeholders are renderedstring320
hoverRow hover background switch (purely visual), "false" disables——
indent-sizePer-level indent of tree data in pxstring24
loadingLoading state: shows placeholder rows in the data area (header retained)boolean—
max-heightCaps the body scroll area with a sticky header (virtual height wins when both set)string—
multi-sortMulti-column sort (JSON array: [{ key, order }], applied in array order)string—
page-sizeRows per page (built-in pagination, default 10)string10
paginationBuilt-in pagination switch (footer pager; leave unset when the host paginates)boolean—
row-draggableRow drag sorting: renders a drag-handle column and dispatches oas-row-reorder on drop; disabled with a warning under virtual scrollingboolean—
row-heightFixed row height for virtual scrolling (px)string40
row-keyUnique key field of a rowstringkey
selectedSet of selected row keys (comma-separated)string—
show-headerToggles the header (default true); with false no header row is rendered while column configuration still aligns data cellsstringtrue
sizeDensity 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-heightstringmedium
sort-keyControlled sort; sort-order is asc / desc / emptystring—
sort-orderControlled sort; sort-order is asc / desc / emptySortOrder—
sticky-rowsNumber of sticky rows (N): the first N rows stick below the header (coexists with the scroll container and fixed columns)string—
stripeZebra striping: alternating light background for odd/even rowsboolean—
summarySummary config [{ key, type: 'sum'|'avg'|'count', label? }], JSON stringstring—
summary-scopeSummary aggregation scope: all (default, full dataset) / page (current page)stringall
table-layoutPasses the table layout algorithm through (with fixed, column widths strictly follow width)string—
totalTotal 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 paginationstring—

Events ​

EventDescription
oas-cell-clickFired on data-cell click, detail: { row, column, value, rowIndex, columnIndex } (suppressed inside interactive controls)
oas-checkCheckbox selection change, detail: { keys: string[] }
oas-column-orderFired after column drag reorder, detail: { keys } (new column order)
oas-column-resizeFired after a column width drag, detail: { key, width }
oas-editInline edit submitted (Enter / blur / operation column save), detail: { rowIndex, key, column, value }; in controlled mode the component does not write back data
oas-edit-cancelInline edit cancelled (Esc / operation column cancel / empty submit restores), detail: { rowIndex, key, column, value } (value is the original value)
oas-expandRow expand/collapse (tree child rows or expandable content rows), detail: { key, expanded }
oas-exportExport triggered (button or exportData), detail: { format, fileName, rowCount }
oas-filter-changeFired when a column filter value changes, detail: { filters } (filters maps a column key to its selected scalar value)
oas-page-changeFired when the built-in page changes, detail: { page, pageSize }
oas-row-clickRow click (also toggles selection when not checkable), detail: { row, key }
oas-row-dblclickFired on data-row double-click, detail: { row, rowIndex }
oas-row-reorderRow 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-scrollVirtual scroll event (rAF throttled), detail: { scrollTop, start, end }
oas-sort-changeSort change, detail: { key, order: 'asc' | 'desc' | '' }

Slots ​

NameDescription
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 VariableDefault
--oas-table-cell-padding-blockvar(--oas-space-3)
--oas-table-cell-padding-inlinevar(--oas-space-4)
--oas-table-font-sizevar(--oas-font-size-md)
--oas-tooltip-bgvar(--oas-color-text-primary)
--oas-tooltip-colorvar(--oas-color-bg)

oas-table-column ​

Attributes ​

AttributeDescriptionTypeDefault
actionsColumn actions (e.g. save/cancel buttons for row editing)——
alignColumn content alignment (left/center/right)——
currencyCurrency symbol for currency-type columns (default ¥)——
data-keyColumn key (escape channel for the Vue reserved word key; plain key also works in native HTML — the component reads both)——
editableColumn editable (double-click a cell to edit)——
editorEditor type/configuration (select/input etc.)——
ellipsisSingle-line truncation with ellipsis for overflowing content (full text on hover title)——
filterableColumn filterable (filter trigger rendered in the header)——
filtersColumn filter options configuration——
fixedColumn fixed (left/right pinning)——
hiddenColumn hidden initially (available in the column visibility panel)——
keyColumn key field name (native HTML writes key directly; Vue templates use data-key)——
mergeAuto-merge adjacent same-value cells in the column——
optionsDisplay 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-numberRow serial number column (1-based, not from data fields)——
sortableColumn sortable (header click cycles asc/desc/none)——
summaryColumn participates in the summary row (sum/avg/count)——
titleColumn header title (defaults to the default slot text)——
typeColumn 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——
widthColumn width (px or CSS value; fixed columns should declare it explicitly)——

Slots ​

NameDescription
defaultCustom cell content (row.field placeholder interpolation inside the template, double-curly syntax)

Note: columns.render is a function type and can only be assigned via the property from JS — it cannot be expressed as a JSON string. For fixed columns it is recommended to declare width explicitly (sticky offsets fall back to 100px when omitted). Summary can also be written directly on a column as summary: 'sum' | 'avg' | 'count'; children (tree child rows) and expand (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.

Released under the MIT OR Apache-2.0 License.