Components

Data Table

Semantic tables with sorting, filtering, selection, pagination, expansion, and resizing.

DataTable renders typed columns and row data as a semantic HTML table. Akaza manages structure and interaction state. Your application supplies data, cell content, and styles.

It supports sorting, filtering, selection, pagination, grouped headers, expandable rows, and pinned columns. It renders a native table and keeps standard table reading behavior for screen readers.

Anatomy

  • #toolbar: Search, filters, view controls, and bulk actions above the table.
  • #caption: Visible or visually hidden table caption.
  • #[column.id]-header: Header content for one column.
  • #header: Generic fallback for every header.
  • #sort-indicator: Sort direction indicator.
  • #[column.id]-cell: Cell content for one column, such as #name-cell.
  • #cell: Generic fallback for every body cell.
  • #expanded: Detail row rendered after an expanded data row.
  • #[column.id]-footer: Footer content for one column.
  • #footer: Generic fallback for every footer cell.
  • #pagination: Replaces the built-in Pagination composition.
  • #loading, #error, #empty: Complete table-body states.
  • #expansion-trigger, #reorder-handle, #column-reorder-handle, #resize-handle: Interaction control content.

Data Table generates the native table structure, controls, state rows, paginator, and result count. Generic #header and #cell slots keep built-in controls. A column slot replaces that part, including any selection, sorting, expansion, or reorder control it contained.

Usage

0 selected
Workspace members
Ada Lovelaceada@example.com
Engineer
active
Grace Hoppergrace@example.com
Admiral
active
Margaret Hamiltonmargaret@example.com
Director
invited
5 results

No action selected.

Examples

Custom headers and cells

Use #[id]-header and #[id]-cell for the clearest template-first API. Each cell receives the row, column, resolved value, indexes, and table controller.

<DataTable :data="users" :columns="columns" row-key="id">
  <template #name-header="{ column, toggleSorting }">
    <button type="button" @click="toggleSorting">{{ column.definition.header }}</button>
  </template>

  <template #name-cell="{ row }">
    <Avatar :src="row.original.avatar" />
    <span>{{ row.original.firstName }} {{ row.original.lastName }}</span>
  </template>

  <template #status-cell="{ value }">
    <StatusBadge :status="value" />
  </template>
</DataTable>

Column render functions are also supported when definitions belong in TypeScript. Slot precedence is specific named slot, generic slot, column render function, then formatted accessor value. Use createDataTableColumnHelper<T>() when a render function needs its accessor value inferred instead of unknown.

import { createDataTableColumnHelper } from "akaza-ui";

const column = createDataTableColumnHelper<User>();

const columns = [
  column.accessor("name", {
    header: ({ column }) => h("span", { class: "min-w-48" }, column.id),
    cell: ({ row }) => h(UserSummary, { user: row.original }),
    ui: { header: "min-w-48", cell: "font-medium" },
    attrs: { cell: ({ row }) => ({ "data-user-id": row.id }) },
  }),
];

Sorting and filtering

Sorting and filters are Vue models. Use controller methods in the toolbar so all changes use the same cancelable event path.

<DataTable
  v-model:sorting="sorting"
  v-model:filters="filters"
  v-model:global-filter="search"
  :data="users"
  :columns="columns"
  row-key="id"
  sorting-mode="multiple"
>
  <template #toolbar="{ table }">
    <input
      :value="search"
      type="search"
      @input="table.setGlobalFilter($event.target.value, $event)"
    >
    <button type="button" @click="table.setFilter('status', 'active', $event)">
      Active only
    </button>
    <button type="button" @click="table.clearFilter('status', $event)">
      All statuses
    </button>
  </template>
</DataTable>

Hold Shift, Control, or Command while activating headers to retain existing sorts in multiple mode.

undefined clears a column filter; null, false, 0, and "" remain available to custom filter functions. The default case-insensitive text matcher treats empty values as no filter. Prefer clearFilter(id) when intent should be explicit. An accepted filter or sort change resets pagination to page 1 as one transaction. Canceling either paired change event cancels the whole transaction.

Selection and bulk actions

Add a selection column and choose single or multiple. Selection stores stable rowKey values, so sorting and filtering never attach state to the wrong row.

<DataTable
  v-model:selection="selectedIds"
  :data="users"
  :columns="columns"
  row-key="id"
  selection-mode="multiple"
  selection-behavior="row"
  :is-row-selectable="row => !row.locked"
  select-all-scope="filtered"
>
  <template #toolbar="{ table }">
    <button :disabled="!table.selectedRows.value.length" @click="archive(selectedIds)">
      Archive selected
    </button>
  </template>
</DataTable>

selectionBehavior="control" changes selection only through checkboxes or radios. "row" also selects from non-interactive row space; links, buttons, inputs, and editable controls remain independent.

Disabled rows render disabled native selection controls. In manual pagination, selectedRows can resolve only keys present in the currently supplied page; keep bulk selection data in application state when selections span server pages.

Pagination

Set paginate for client pagination. The bundled paginator is styled through the pagination* UI keys or replaced through #pagination.

<DataTable
  v-model:pagination="pagination"
  :data="users"
  :columns="columns"
  row-key="id"
  paginate
>
  <template #pagination="{ page, pageCount, setPage }">
    <button :disabled="page === 1" @click="setPage(page - 1)">Previous</button>
    <span>Page {{ page }} of {{ pageCount }}</span>
    <button :disabled="page === pageCount" @click="setPage(page + 1)">Next</button>
  </template>
</DataTable>

When filtering or a smaller data set removes the current page, DataTable clamps v-model:pagination to the last available page. page and pageSize accept finite numbers only.

Server-controlled data

Enable manual modes when supplied data is already processed by an API. Akaza updates models but does not sort, filter, or slice rows again. Change events fire before model commits so details.cancel() can reject a change; do not fetch from those events. Observe committed Vue state instead, and cancel stale requests.

<script setup lang="ts">
const sorting = ref([]);
const filters = ref({});
const pagination = ref({ page: 1, pageSize: 25 });
const query = computed(() => ({
  sorting: sorting.value,
  filters: filters.value,
  pagination: pagination.value,
}));

watch(query, async (value, _previous, onCleanup) => {
  const request = new AbortController();
  onCleanup(() => request.abort());
  response.value = await fetchRows(value, request.signal);
}, { immediate: true });
</script>

<template>
  <DataTable
  v-model:sorting="sorting"
  v-model:filters="filters"
  v-model:pagination="pagination"
  :data="response.rows"
  :columns="columns"
  :row-count="response.total"
  row-key="id"
  manual-sorting
  manual-filtering
  manual-pagination
  />
</template>

rowCount is the total server result count, not current page length. selectAllScope="filtered", filteredRowCount, and client-side selected-row resolution cover loaded rows only in manual modes. Use globally stable rowKey values across every page.

Grouped headers and details

Nested column definitions generate proper colspan, rowspan, and scope values. An expander column controls detail rows.

const columns = [
  { id: "expand", type: "expander", header: "" },
  {
    id: "identity",
    header: "Identity",
    children: [
      { accessor: "name", header: "Name" },
      { accessor: "email", header: "Email" },
    ],
  },
];
<DataTable
  v-model:expanded="expandedIds"
  :data="users"
  :columns="columns"
  row-key="id"
  :is-row-expandable="row => Boolean(row.auditLog.length)"
>
  <template #expanded="{ row }">
    <AuditLog :entries="row.original.auditLog" />
  </template>
</DataTable>

Use getSubRows instead when data is hierarchical and child records should become ordinary nested table rows.

Set rowHeader: true on the identifying column to render its body cells as native <th scope="row"> elements.

Column layout

Sizing, order, visibility, and pinning use separate models. Pointer and keyboard input update those models.

<DataTable
  v-model:column-sizing="columnSizing"
  v-model:column-order="columnOrder"
  v-model:column-visibility="columnVisibility"
  v-model:column-pinning="columnPinning"
  :data="users"
  :columns="columns"
  row-key="id"
  sticky-header
  column-reorder
  :column-resize="{ behavior: 'fit', update: 'change', keyboardStep: 12 }"
>
  <template #toolbar="{ table }">
    <button @click="table.setColumnVisible('email', false, $event)">Hide email</button>
    <button @click="table.setColumnPinned('name', 'start', $event)">Pin name</button>
  </template>
</DataTable>

Column sizes use pixels and a fixed table layout. expand changes total table width. fit transfers width to an adjacent column. Pinned headers sit above pinned body and footer cells. Consumer min-width and width rules can override calculated sizes.

Row reorder is available only for flat, unsorted rows. Sorting or hierarchical depth disables native reorder buttons and controller moves, avoiding ambiguous source order. Filtered and paginated moves still operate on stable row keys in the full supplied array.

Loading, error, and empty states

State rows preserve the table structure and span every visible column. Replace their content without rebuilding the table.

<DataTable :data="rows" :columns="columns" row-key="id" :loading="pending" :error="error">
  <template #loading>Loading members...</template>
  <template #error="{ error }">{{ getMessage(error) }}</template>
  <template #empty>No members match these filters.</template>
</DataTable>

API Reference

Models

ModelTypeDefaultDescription
v-model:selectionrowKey | rowKey[] | nullnullSelected stable row keys.
v-model:sortingDataTableSorting[][]Ordered sort descriptors.
v-model:filtersRecord<string, unknown>{}Per-column filter values.
v-model:globalFilterstring""Global search text.
v-model:pagination{ page, pageSize }{ page: 1, pageSize: 10 }One-based page state.
v-model:expandedrowKey[][]Expanded row keys.
v-model:columnVisibilityRecord<string, boolean>{}Column visibility overrides.
v-model:columnOrderstring[][]Leaf column order.
v-model:columnSizingRecord<string, number>{}Column widths in pixels.
v-model:columnPinning{ start: string[], end: string[] }empty sidesPinned leaf columns.
v-model:rowOrderrowKey[][]User-controlled row order.

Props

PropTypeDefaultDescription
datareadonly T[]requiredRows supplied to the table.
columnsreadonly DataTableColumn<T>[]requiredTyped column definitions.
rowKeykeyof T | (row, index) => string | numberrequiredStable row identity.
captionstringNative table caption text.
ariaLabelstringAccessible table label when no caption is used.
ariaLabelledbystringID of an external table label.
ariaDescribedbystringIDs describing the table.
loadingbooleanfalseRenders loading state and aria-busy.
errorboolean | string | ErrorfalseRenders error state.
emptyLabelstring"No results."Default empty-state text.
errorLabelstring"Unable to load results."Default non-Error message.
sortingMode"single" | "multiple""single"Number of retained sort descriptors.
manualSortingbooleanfalseDisables client sorting.
manualFilteringbooleanfalseDisables client filtering.
paginatebooleanfalseEnables client pagination.
manualPaginationbooleanfalseTreats supplied data as an API page.
rowCountnumberTotal server row count for manual pagination.
showPaginationbooleantrueShows built-in or slotted pagination.
selectionMode"none" | "single" | "multiple""none"Selection behavior.
selectionBehavior"control" | "row""control"Whether non-interactive row clicks select.
selectAllScope"page" | "filtered""page"Rows affected by select all.
isRowSelectable(row) => booleanall rowsDisables selection per row.
isRowExpandable(row) => booleanrows with childrenEnables detail expansion per row.
getSubRows(row) => readonly T[]Returns hierarchical child rows.
columnResizeboolean | DataTableColumnResizeOptionsfalseEnables pointer/touch/keyboard resizing.
columnReorderbooleanfalseEnables pointer/touch/keyboard column reorder handles.
rowReorderbooleanfalseEnables pointer/touch/keyboard row reorder handles.
stickyHeaderbooleanfalseKeeps headers at the viewport top.
dir"ltr" | "rtl""ltr"Logical direction for sizing and pinning.
labelsDataTableLabelsEnglish labelsAccessible control and state labels.
rowAttrsHTMLAttributes | (row) => HTMLAttributesNative attributes/classes for every row.
uiDataTableUiClasses for structural parts.

Column Shape

KeyTypeDescription
idstringStable column ID. Required without a string accessor.
type"data" | "display" | "selection" | "expander" | "reorder"Column behavior.
accessorkeyof T | (row, index) => valueResolves a data value.
headerstring | (context) => VNodeChildDefault header content.
cell(context) => VNodeChildDefault cell renderer.
footerstring | (context) => VNodeChildFooter content.
childrenDataTableColumn<T>[]Nested grouped columns.
sortablebooleanEnables header sorting. Defaults to true for accessor columns.
compare(a, b, rowA, rowB) => numberCustom client comparator.
filterablebooleanIncludes column in filters/global search.
filter(value, filterValue, row) => booleanCustom client filter.
size, minSize, maxSizenumberWidth constraints in pixels.
resizable, reorderablebooleanDisables an enabled table-level interaction per column.
pinned"start" | "end" | falseInitial logical pin side.
hiddenbooleanInitial visibility.
rowHeaderbooleanRenders body cells as <th scope="row">.
ui{ column, header, cell, footer }Classes for this whole column or one section.
attrs{ header, cell, footer }Native attributes or context callbacks.
metadataRecord<string, unknown>Application metadata returned on the definition.

Emits

Every model emits its update:* event after an accepted change. The paired *-change event fires first with (value, details); details.cancel() prevents the model update.

EventDescription
selection-changeSelection changed.
sorting-changeSort descriptors changed.
filters-changeColumn filters changed.
global-filter-changeGlobal filter changed.
pagination-changePage or page size changed.
expanded-changeExpanded row keys changed.
column-visibility-changeVisibility map changed.
column-order-changeLeaf column order changed.
column-sizing-changeWidth map changed.
column-pinning-changePin sides changed.
row-order-changeRow order changed.
reset-changeAtomic reset requested. Canceling it preserves every model.
row-clickNative row click with row and cancelable details.
row-contextmenuNative row context-menu event with row details.
cell-clickNative cell click with cell context and details.

Slots

SlotPropsDescription
toolbartableControls above the viewport.
captionNative caption content.
[column.id]-headerheader contextSpecific header renderer.
headerheader contextGeneric header renderer.
sort-indicatorcolumn, sortedSort direction content.
[column.id]-cellcell contextSpecific cell renderer.
cellcell contextGeneric cell renderer.
expandedrow, tableDetail row content.
[column.id]-footercolumn, tableSpecific footer content.
footercolumn, tableGeneric footer content.
paginationtable, page, pageSize, pageCount, setPage, setPageSizeReplaces default paginator.
loadingtableLoading row content.
errorerror, tableError row content.
emptytableEmpty row content.
expansion-triggerrowExpand/collapse control content.
reorder-handlerowRow drag handle content.
column-reorder-handlecolumnColumn drag handle content.
resize-handlecolumnResize handle content.

Controller

#toolbar, cell/header contexts, #pagination, useDataTable(), and the exposed table property use the same Vue-native controller.

MemberDescription
columns, headerGroups, rows, selectedRowsRead-only computed table structures.
filteredRowCount, pageCount, page, pageSizeRead-only result and pagination state.
model refsThe eleven public model refs listed above.
getColumn(id), getRow(id)Looks up wrapped public instances.
getRowCells(row)Returns visible cell contexts.
setFilter(id, value, event?), clearFilter(id, event?), setGlobalFilter(value, event?)Updates filters.
toggleSorting(id, event?)Cycles ascending, descending, and none.
toggleRow(row, event?), toggleAllRows(event?)Updates selection.
isAllRowsSelected(), isSomeRowsSelected()Selection summary for controls.
toggleExpanded(row, event?)Updates detail/child visibility.
setPage(page, event?), setPageSize(size, event?)Updates pagination.
setColumnVisible(id, visible, event?)Updates visibility.
setColumnPinned(id, side, event?)Updates logical pinning.
resizeColumn(id, size, event?)Applies width constraints and resize behavior.
moveColumn(id, targetId, event?), moveRow(id, targetId, event?)Reorders by stable IDs.
reset(event?)Atomically restores model defaults and definition-level visibility/pinning. Returns false when canceled.

The component instance exposes getColumn, getRow, setFilter, clearFilter, setGlobalFilter, setPage, setPageSize, and reset.

UI Options

KeyDescription
root, toolbar, viewport, table, caption, colgroup, colOuter table structure.
thead, headerRow, headerCell, headerContentHeader structure.
sortTrigger, sortIndicator, resizeHandle, columnReorderHandleHeader controls.
tbody, row, cell, cellContentBody structure.
selectionControl, expansionTrigger, reorderHandleRow controls.
expandedRow, expandedCellExpanded detail row.
loadingRow, loadingCell, errorRow, errorCell, emptyRow, emptyCellBody states.
tfoot, footerRow, footerCellFooter structure.
paginationPaginator wrapper.
paginationRoot, paginationList, paginationItemNested Pagination structure.
paginationFirst, paginationPrevious, paginationPage, paginationEllipsis, paginationNext, paginationLastNested Pagination controls.
liveRegionVisually hidden result announcement.

Styling Hooks

UI keyCSS classData attrs
rootakaza-data-tabledata-akaza-state="loading|error|ready|empty", data-akaza-loading, data-akaza-error, data-akaza-sticky-header
toolbarakaza-data-table-toolbar
viewportakaza-data-table-viewportNative overflow viewport.
tableakaza-data-table-tablearia-busy
captionakaza-data-table-caption
colgroup, colakaza-data-table-colgroup, akaza-data-table-coldata-akaza-column, data-akaza-pinned
theadakaza-data-table-theaddata-akaza-sticky
headerRowakaza-data-table-header-rowdata-akaza-depth
headerCellakaza-data-table-header-celldata-akaza-column, data-akaza-sortable, data-akaza-state, data-akaza-pinned, aria-sort
headerContentakaza-data-table-header-content
sortTrigger, sortIndicatorakaza-data-table-sort-trigger, akaza-data-table-sort-indicatorNative button attrs.
resizeHandleakaza-data-table-resize-handleseparator role and value ARIA.
columnReorderHandleakaza-data-table-column-reorder-handleNative button attrs.
tbodyakaza-data-table-tbody
rowakaza-data-table-rowdata-akaza-row-key, data-akaza-row-index, data-akaza-depth, data-akaza-state, data-akaza-selected, data-akaza-expanded, data-akaza-disabled
cellakaza-data-table-celldata-akaza-column, data-akaza-column-index, data-akaza-pinned
cellContentakaza-data-table-cell-content
row controlsakaza-data-table-selection-control, akaza-data-table-expansion-trigger, akaza-data-table-reorder-handleNative input/button ARIA.
expanded partsakaza-data-table-expanded-row, akaza-data-table-expanded-celldata-akaza-state="expanded"
state partsakaza-data-table-loading-*, akaza-data-table-error-*, akaza-data-table-empty-*matching data-akaza-state on rows.
footer partsakaza-data-table-tfoot, akaza-data-table-footer-row, akaza-data-table-footer-cellcolumn/pinning attrs on cells.
paginationakaza-data-table-pagination
nested pagination keysakaza-pagination-*See Pagination.
liveRegionakaza-data-table-live-regionaria-live="polite".

Plain class applies to the outer root. Use ui.table, section keys, or per-column ui and attrs for granular control. DataTable ships only behavior-critical layout for overflow, sizing, sticky/pinned positioning, resize targets, and the live region.

Keyboard

ControlKeyBehavior
sort headerEnter / SpaceCycles sort direction through its native button.
selection controlSpaceSelects a row or the configured select-all scope.
expansion controlEnter / SpaceOpens or closes row details/children.
resize separatorArrowLeft / ArrowRightChanges width by keyboardStep, respecting RTL.
resize separatorHomeRestores the definition width or 150px.
column reorder handleArrowLeft / ArrowRightMoves the column one visible position.
row reorder handleArrowUp / ArrowDownMoves the row one visible position.
paginationTab, Enter, SpaceUses native Pagination button/link behavior.

Pointer dragging and touch Pointer Events use the same resize and reorder actions. The table does not add spreadsheet-style arrow navigation to ordinary data cells; native table reading and normal document tab order remain intact.