Data Table
DataTable combines a toolbar, a table of records and a pagination footer. Use it for model records, activity lists and report results that need consistent browsing controls. Use Craft’s element index for ordinary element listings and Editable Table for values being authored in rows.
Browsing Records
Supply rows with stable IDs and column definitions. The default client mode searches, sorts, filters and pages the complete supplied dataset.
Sorting
Sorting is opt-in per column:
- Click the active sortable heading to toggle ascending and descending.
- Click another sortable heading to sort that column ascending.
- Tables without sortable columns retain their supplied order.
A table with sortable columns always has an active sort. Supply query.sort, set defaultSort, or let the table use its first sortable column ascending. The same resolved sort is sent to remote loaders and query listeners. Passing an empty sort to setQuery() restores the default; it does not introduce an unsorted state.
View and Columns
The View popup combines the sort field, direction and column preferences:
- Reorder: drag a column handle to preview its new position, then release to apply the order. Press Escape to cancel, or focus the handle and press Up/Down to reorder with the keyboard.
- Show or hide: toggle columns without changing the underlying rows.
hideable: falsekeeps required columns visible; at least one column always remains visible. - Reset: Use defaults restores
defaultViewanddefaultSort. Unless supplied, these defaults show all columns in definition order and sort the first sortable column ascending.
import '@verbb/plugin-kit-web/components/data-table.js';
const table = document.querySelector('pk-data-table');
table.label = 'Requests';
table.columns = [
{ id: 'name', label: 'Participant', sortable: true, hideable: false },
{ id: 'status', label: 'Status', type: 'status' },
];
table.rows = [
{ id: 1, name: 'Alex Morgan', status: 'pending' },
{ id: 2, name: 'Jamie Ellis', status: 'active' },
];Set rowKey to another property name or a function when IDs are stored differently. Keys must be unique and stable strings or numbers; never use a page-relative array index. Assign new arrays/objects when changing rows, columns or query state.
Tables without Selection
Selection is off by default. These product and activity examples have no checkbox column or bulk-action area. They still support search, sorting, column preferences and pagination. Set query.pageSize to control the page length; the footer keeps paging and the result count together without a page-size selector.
Index and Compact Layouts
Use layout="index" for long record lists. The toolbar stays at the top of the nearest scrolling container. Column headers scroll with the table rows. The footer stays at the bottom, with a synchronized horizontal scrollbar when columns overflow. The example below uses a 420px-tall scroll container with 2px of padding to keep the table frame’s outer border visible. Scroll down, move sideways, hide a column in View, and select rows to compare the pinned controls. Set --pk-data-table-header-offset or --pk-data-table-footer-offset when surrounding controls occupy the scrollport edges. --pk-data-table-toolbar-background sets the sticky toolbar background. The default layout="embedded" keeps the toolbar, headers and footer in normal document flow.
Use size="compact" for denser settings lists. Selection remains optional in either size or layout.
Long Content and Overflow
Ordinary text and custom cells wrap by default, with Craft's 33vw maximum cell width. Long unbroken values can break within the cell. Built-in link columns stay on one line and show an ellipsis when their labels exceed the available width; the full label remains in the link's accessible name and native tooltip. Set wrap: true on a link column to show its complete label over multiple lines.
Dates, numbers, statuses and handles stay on one line by default. Set wrap: false on a text column when a code or other value must stay intact. The width option is a preferred width, not a fixed limit: automatic table layout can widen a column to accommodate headings and non-wrapping content.
When the columns are wider than the available space, the table scrolls horizontally inside its own region. Search, View and the footer stay in place. Embedded tables use their native scrollbar; layout="index" adds a synchronized scrollbar above the sticky footer so it remains within reach on tall lists. Trackpad scrolling and keyboard scrolling work in both left-to-right and right-to-left layouts.
The example below combines a long linked title, wrapping prose, an unbroken reference and an explicitly non-wrapping code. Scroll down and sideways to review the sticky controls.
Export Menu
Supply exportFormats to show the footer’s Export dropdown. Each option has a value, label and optional disabled flag. The component emits pk-export once when a format is chosen; it does not generate a file or fetch all matching records. The product example above displays the request so you can try the hook.
table.exportFormats = [
{ value: 'csv', label: 'CSV' },
{ value: 'json', label: 'JSON' },
];
table.addEventListener('pk-export', (event) => {
const { format, query, view, columnIds, selectedKeys } = event.detail;
// Pass this snapshot to the plugin’s export handler.
});The event contains a snapshot of query/view state, ordered visible columnIds and explicitly selected IDs. The consuming plugin decides whether to export the current page, matching records or selected records, and enforces server permissions. Set exporting while handling the request to disable the menu and show progress. Omit exportFormats to hide it. React uses onPkExport; Vue uses @pk-export.
Columns and Custom Cells
The examples below cover the built-in column types. text is the default when type is omitted. Empty built-in values display an em dash.
| Type | Example | Configuration |
|---|---|---|
text | Team in the member directory | Plain text that wraps by default; use wrap: false to keep it on one line. |
link | Member names and email addresses | href(row) provides the destination while the cell value supplies the label. |
handle | Field handles and machine names | Monospaced pill with a copy button for the complete value. |
status | Approved, Awaiting review and Inactive | status(row) maps the visible label to a badge colour and indicator. |
number | Orders, revenue and conversion | numberOptions controls integer, currency and percentage formatting. |
date | Campaign update dates | dateOptions, locale and timeZone control the displayed date. |
custom | Participant name with a secondary email line | renderCell or a named cell slot supplies the content. |
Handles
Set type: 'handle' for machine-readable identifiers such as field or filesystem handles. The value appears in a bordered monospace pill with a clipboard button; copying keeps row selection unchanged and briefly shows a checkmark. Empty values show an em dash. Handles stay on one line by default; set wrap: true to allow long values to wrap. Override labels.copyHandle and labels.copiedHandle to translate the copy control. The actions example includes a handle column.
table.columns = [
{ id: 'handle', label: 'Handle', type: 'handle', sortable: true },
];Links, Text and Status
Member names are ordinary web links, while email addresses use mailto:. The member URLs use example.com as placeholder destinations; replace them with your application's record URLs. Link columns also support relative URLs and tel: links. Keep the displayed value separate from the destination with href(row).
Status columns render a StatusLabel badge with a dot and visible text. Here, status(row) maps Approved to active, Awaiting review to pending and Inactive to inactive, so the label can use your product's terminology.
Status Labels
Use the same badge treatment for both semantic states and custom workflows. This example maps order workflow and payment labels to different colours through status(row). Select a row to see how the badges adapt to the selected background. Keep the row values as readable labels; the component handles the uppercase visual treatment.
Numbers and Dates
Orders use grouped digits, revenue uses Australian dollars, and conversion uses a percentage. The conversion column computes its numeric value with value(row), so sorting uses the ratio rather than its formatted text. The table supplies locale="en-AU" and timeZone="Australia/Melbourne" for consistent formatting. The last row demonstrates that zero remains visible and a missing date displays an em dash.
Custom Cells
For content beyond those defaults, renderCell({ row, column, value, rowKey }) returns a Lit template, DOM node or text. This controls the cell content while the component still owns table layout, sorting, selection and accessibility. Sorting/searching use the column’s value, not its rendered markup.
import { html } from 'lit';
table.columns = [{
id: 'name',
label: 'Participant',
sortable: true,
renderCell: ({ row }) => html`<strong>${row.name}</strong>`,
}];Alternatively set type: 'custom' and project content into the slot returned by dataTableCellSlot(rowKey, columnId), exported from the data-table family. React supports React-node render callbacks; Vue supports scoped cell-columnId slots. Consumer-projected content uses the consumer’s styles; built-in and Lit-rendered cells use the component’s shadow-root styles. Do not pass untrusted HTML strings as markup.
Filters and Toolbar Extensions
Define filters independently from visible columns, then open the funnel button inside search. On wide tables the builder opens beneath the search field; in narrower spaces it opens as a centered dialog. Choose Add a filter, select a comparison and enter its value. Each field can appear once. Use the field selector to replace a condition, or the remove button to delete it. All conditions must match; multiple values within an “is one of” condition are alternatives.
Changes remain in a draft until Apply. Applying resets pagination and selection once, then updates local results or calls the remote loader. Cancel, Escape and clicking outside discard the draft. Clear filters removes the draft conditions; choose Apply to clear the active filters. Empty value conditions are ignored unless their operator is is empty or has a value. Reversed ranges display an error and keep the builder open. External changes to the query or filter definitions replace an open draft with the current applied state.
Filter Types and Comparisons
This example provides all six filter types. Try Status equals Active, Requests is at least 20, and Created is between two dates. Add Team is one of to choose multiple teams, or Verified equals No to filter a boolean value. Team is empty finds Jordan Lee. Use Participant begins with for a text comparison.
| Type | Default Comparison | Other Comparisons |
|---|---|---|
text | contains | equals, not-equals, not-contains, starts-with, ends-with, empty, not-empty |
select | equals | not-equals, empty, not-empty |
multi-select | in | not-in, empty, not-empty |
boolean | equals | empty, not-empty |
number-range | between | equals, not-equals, greater-than, at-least, less-than, at-most, empty, not-empty |
date-range | between | The numeric comparisons above, labelled as before/after dates, plus empty and not-empty. |
Select options use { value, label }. Boolean filter values are the strings 'true' and 'false'; row values may be booleans. A multi-select value is an array of selected option values. A between value is [from, to], with empty strings representing unbounded ends. Other comparisons use a string. Empty comparisons retain the filter key with an empty string value. Local text comparisons are case-insensitive; select values compare exact option IDs. Date comparisons use UTC calendar days and include the whole selected end day. Interpret dates at the server when your product needs site-time-zone boundaries.
query.filters stores values, and the optional query.filterOperators map stores non-default comparisons. Omitting an operator preserves the filter type’s default comparison. Limit a filter's available comparisons with operators; its first supported entry becomes the default. For example:
table.filters = [{
id: 'requests', label: 'Requests', type: 'number-range',
operators: ['at-least', 'at-most', 'between'],
}];
table.setQuery({
filters: { requests: '20' },
filterOperators: { requests: 'at-least' },
});Manage active filters in the filter popover. The funnel button shows the number of active filters; adding, changing, removing and clearing conditions all happen in the popover and take effect when you choose Apply. Search is debounced by searchDelay (250 ms by default); its clear button resets search immediately and returns focus to the input. Labels, comparison names, action text and validation messages can be translated through labels.
Remote Filters
A remote endpoint receives both filters and filterOperators. Validate field IDs and supported operators before applying them to a database query. The example below simulates a remote loader and displays its request count and filter payload. Editing conditions or cancelling makes no request; Apply sends the completed draft once. Kit does not interpret remote-only criteria.
Toolbar and Custom Criteria
Use toolbar-start for controls before search, toolbar-controls for controls between search/filtering and View, and toolbar-end for actions after View. The slots remain available when searchable or columnControls is false; hideToolbar hides the whole toolbar. The consumer owns each control's state and actions. For multiple adjacent controls, supply a ButtonGroup or a flex wrapper.
This example uses a status selector before search, density buttons before View, and a new-record action at the end. The density buttons change size; a custom cards layout would remain consumer-owned.
Use the filters slot with showFilters for consumer-owned criteria such as a Craft condition builder or asynchronous user selector. This slot appears inside the shared builder, but its controls own their state and application; the built-in Apply/Cancel draft only covers declared filters. Call setQuery({ filters: nextFilters, filterOperators: nextOperators }) for custom values, or setQuery({ criteria: conditions }) for structured remote conditions. The local helper ignores undeclared filter IDs and does not interpret criteria. Clearing built-in filters leaves consumer-owned criteria unchanged.
hideToolbar supports embedded results with external controls. searchable, columnControls and hideFooter allow smaller compositions. The entire table remains one component, so consumers do not need to rebuild pagination or toolbar spacing.
Selection and Actions
Enable selectable for row selection and checkboxes. Row and checkbox clicks follow Craft’s element index checkbox mode. The command modifier is Cmd on macOS and Ctrl on Windows/Linux.
| Gesture | Selection behaviour |
|---|---|
| Click a row’s non-interactive content or its checkbox | Toggle that record, preserving other selected records. Clicking a selected record deselects it. |
| Cmd/Ctrl-click a row or its checkbox | Select only that record, clearing every other selected ID. Clicking it again keeps it selected. |
| Shift-click, including Cmd/Ctrl+Shift-click | Select the inclusive range from the anchor to the clicked record, skipping ineligible records. Replace the current page’s selection; retain selected IDs on other pages. |
| Space or Enter on a row checkbox | Toggle that record. Adding Shift or the command modifier does not change selection. |
| Up/Down on a row checkbox | Move focus between eligible records without changing selection, including with Shift held. Cmd/Ctrl+Up/Down moves to the first/last eligible record. Home/End also moves to the first/last eligible record. |
| Cmd/Ctrl+A on a row checkbox | Select all eligible records on the current page, preserving selected IDs on other pages. |
| Header checkbox | Select or deselect all eligible records on the current page, preserving selected IDs on other pages. |
Selecting an individual record establishes the range anchor. Deselecting the anchor clears it; deselecting another record leaves it in place. Range selection keeps the same anchor, while selecting the whole page anchors at the first eligible record. Deselecting through the header checkbox keeps the anchor. Paging resets the anchor. Without an anchor on the current page, Shift-click follows the normal click behaviour (or selects exclusively when Cmd/Ctrl is also held). Escape does not clear selection.
Set rowLabel to a property name or formatter to give selection checkboxes readable names instead of record IDs. Links, buttons, inputs and other interactive cell content keep their own behaviour. Add data-table-interactive to a custom interactive cell wrapper if it has no native control semantics. These gestures do nothing when selectable is false.
Selected rows use a dark slate background with white text. The selection count and selection-actions slot sit in a reserved footer region, so selecting records never inserts content above the table or shifts its rows. Keep the footer enabled when providing selection actions. --pk-data-table-selected-background and --pk-data-table-selected-color customize the selected colors; custom cells can use --pk-data-table-cell-color and --pk-data-table-cell-muted-color to follow the row’s state.
The header checkbox selects only eligible records on the current page. Explicit IDs remain selected while paging, and the count shows their full scope. Changing search, filters or sorting clears selection. isRowSelectable(row) can exclude records. On a site/account change, clear selectedKeys explicitly before supplying new data.
Provide rowActions(row) to populate the trailing actions column. Use preset: 'edit', 'move' or 'delete' for Kit's inline pencil, move and × icon buttons. Each action has an id; presets supply a translated default label, and label can override it. disabled: true keeps an unavailable action visible but inactive. Omit an action from the returned array to hide it; an empty array renders no controls for that row.
table.rowActions = (row) => [
{ id: 'edit', preset: 'edit' },
{ id: 'delete', preset: 'delete', disabled: row.protected === true },
{ id: 'duplicate', label: 'Duplicate' },
];Custom actions appear in the overflow menu by default. Set display: 'menu' to put a preset in that menu, or display: 'inline' to show a custom action as a text button. Inline controls use the action label and rowLabel for their accessible names. Translate preset defaults with labels.editAction, labels.moveAction and labels.deleteAction, and their accessible-name pattern with labels.rowAction.
pk-row-action supplies { action, row, rowKey }, where action is the configured ID. The consuming application handles navigation, confirmations, permissions, endpoints and mutations. The move preset is a button for a consumer-defined operation such as opening a destination picker; it does not enable drag reordering, persist an order or disable sorting. Use manual row ordering for drag handles and keyboard reordering.
Only provide a move action when your application implements it. The sortable example above omits it; the manual-order example below provides working drag and keyboard controls.
Put bulk controls in selection-actions, and consume pk-selection-change.detail.selectedKeys. There is no implicit “all matching records” selection. Exports and bulk operations belong to the consuming plugin, which knows their scope and backend guarantees.
Dialogs and Popovers from Cells
An email plugin can let editors preview a message without leaving the list, or inspect delivery counts beside a row. Preview email opens a Dialog with that record's sample message; Delivery summary opens a Popover anchored to its button. Try different rows to see the content change, and use Done, Escape or the popover's outside-dismiss behaviour to return to the table.
The renderCell functions return ordinary buttons and a Popover. The preview button fills one shared Dialog outside the table and opens it; the example remembers the clicked control and restores focus after the dialog closes.
These are composition examples implemented by the consuming plugin. DataTable supplies the custom-cell extension point; your application owns overlay content, open state, data loading and any save actions. The demo uses local sample data and sends no email. The same pattern works for inspecting a job payload in a dialog or showing a short activity summary in a popover.
Manual Row Ordering
Enable manualOrder (manual-order in HTML) when the supplied row sequence is the record order. The table adds a drag-handle column at the end, after any row actions, and removes column-sorting controls from the headers and View menu. Existing query.sort is cleared; defaultSort, saved sort preferences and calls to setQuery({ sort: … }) cannot override manual order. Column visibility and column reordering remain available. Turning manual order off resumes the usual default sorting.
Drag a handle to move a row within the current page. Focus a handle and press Up or Down to move one position, or Home/End to move to the beginning/end of that page. The moved handle keeps focus and its new position is announced. Escape, cancelled pointer capture, or a data/view change cancels a drag without emitting a move. Dragging near a scroll-container edge scrolls it automatically.
The example simulates a consumer save and can restore the previous order after a failure. The table updates its row array immediately and emits pk-row-reorder; it does not call an endpoint or persist anything. Set reorderDisabled while your save is pending to prevent overlapping moves. In React or Vue, mirror event.detail.rows into your state so later renders retain the new order. DataTableRowReorderDetail is exported by all three packages.
| Detail | Meaning |
|---|---|
rowKey | Stable key of the moved row. |
fromIndex, toIndex | Zero-based positions within the displayed page. |
orderedKeys, previousKeys | New and previous key sequences for this page only. Numeric and string keys retain their types. |
rows, previousRows | New and previous complete arrays in client mode, or current-page arrays in remote mode. The arrays are detached; row objects retain their identity. |
query | Detached query snapshot identifying the page/cursor and its scope. |
Local pagination preserves the positions of rows on other pages. In remote mode, send the page's ordered keys, previous keys and query to your own endpoint; never treat these keys as the entire remote dataset. Your application must define how that page maps to stored positions, validate permissions and detect concurrent changes. Cross-page dragging is not supported.
On a failed local or externally fetched save, restore previousRows to the table or framework state. With dataSource, call refresh() to reload the current page from the authoritative backend. Keep save errors in your application's feedback UI; the table's error property represents load failures. If the user can navigate during saving, use the captured query to apply the result to the appropriate page.
Reordering is disabled while loading, after a load error, when fewer than two rows are displayed, or while search, built-in filters or consumer-owned query.criteria are active. Search and filter controls still work; clear them to resume ordering. This avoids changing the order around records hidden by the current query. Use reorderDisabled for any additional product-specific restrictions.
Remote Data
Set mode="remote" and provide a dataSource(query, { signal }) returning a promise for { rows, total?, hasNext?, nextCursor?, previousCursor? }. The loader runs on initial connection and query changes. Kit cancels superseded requests and rejects stale responses even when a loader ignores its signal. When sorting, existing rows stay visible and a spinner replaces the active column’s chevron until the request finishes. Other requests show a centred body spinner in place of the visible rows, preserving their space. Row interactions remain disabled while loading. Initial loads and loads following an empty result use the same space as the empty state. Loading text is announced to screen readers without a visible label. Loading does not insert a message above the table or move the footer. Paging keeps keyboard focus on an available page button when the request completes, unless the user has moved focus elsewhere.
table.mode = 'remote';
table.dataSource = async (query, { signal }) => {
// Replace this URL and transport with the plugin's authenticated endpoint.
const response = await fetch('/api/requests', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(query),
signal,
});
if (!response.ok) throw new Error('Request failed');
return response.json();
};The example endpoint is illustrative. Craft plugins should use their existing authenticated request/CSRF transport. The server validates fields, operators, sort capabilities, page limits and permissions, then searches, filters and sorts the full authorised dataset. Remote mode never applies those operations again to the returned page. Catch pk-load-error for diagnostics; the UI uses safe error copy. refresh() retries or reloads after a mutation.
An existing application can own requests instead: omit dataSource, listen to pk-query-change, then assign rows, total or hasNext, error and loading. A UI query change sets loading to true; the application must clear it when the matching response completes. It also owns cancellation and stale-response handling in this mode. DataTableRequestController is available as a small optional request coordinator. Handle pk-retry to retry externally controlled requests.
Cursor Pagination
Set pagination="cursor" for endpoints that return opaque cursors rather than page numbers. Kit forwards the requested token through query.cursor. Return nextCursor when another page exists and previousCursor when returning is supported. The table does not fabricate a total, page count or random-access links. Search/filter/sort changes clear the cursor. query.pageSize controls the requested page length and remains available to the loader.
Empty and Error States
An empty dataset and a filtered query with no matches have distinct messages. Loading and updating feedback are announced. Failed loads show retry while preventing actions against stale rows. Override labels to translate UI text; interpolation keys are retained in translated strings. Use the empty slot for a product-specific next step.
State and Persistence
query contains search, sort, filters, optional filterOperators, zero-based pageIndex, pageSize and optional cursor. view contains hiddenColumns and columnOrder. The corresponding events expose complete new state. Consumers decide whether to put this state in the URL, user preferences or a named view. The table does not write browser storage or history automatically; opt into browser persistence with the helper below.
Column visibility is a viewing preference; it does not alter saved report definitions or permission boundaries. Keep a report’s persisted columns and export settings under the report editor’s explicit save behaviour.
Remembering View Preferences
Call persistDataTableView(table, options) to remember column visibility, column order and sort field/direction in local storage. Configure the table first, then attach the helper before connecting it or starting its remote loader. Restoration is synchronous, so the first request uses the remembered sort without loading twice.
import { PkDataTable, persistDataTableView } from '@verbb/plugin-kit-web/components/data-table.js';
const table = new PkDataTable();
table.columns = columns;
table.defaultSort = [{ id: 'subscribed', direction: 'desc' }];
table.query = queryFromUrl;
const preferences = persistDataTableView(table, {
key: 'back-in-stock:subscriptions',
userId: currentUserId,
version: 1,
restoreSort: !urlHasExplicitSort,
});
table.dataSource = loadSubscriptions;
container.append(table);
// When disposing of this table or switching accounts:
// preferences.disconnect();| Option | Behaviour |
|---|---|
key | Required stable table identity. Include site or context when those views should have separate preferences. |
userId | Required current account identifier. Users sharing a browser get separate preferences. |
version | Defaults to 1. Change it to start fresh after an incompatible consumer schema change. |
restoreSort | Defaults to true. Set to false when an explicit URL sort should win; column preferences still restore and later user sort changes still save. |
storage | Optional synchronous getItem/setItem/removeItem adapter. Defaults to the table document's localStorage. |
Use defaults removes the saved preference when the view and sort match the configured defaults. Search, filters, pagination, cursors, selected rows and row data are never stored. Browsers and origins have separate storage; this does not synchronise preferences across devices.
Removed column IDs and invalid sort fields are ignored. Required columns stay visible, new columns are appended in definition order, and at least one column remains visible. Invalid JSON, blocked storage and quota failures leave the table usable without persistence.
The helper listens to pk-view-change and sort changes in pk-query-change. Direct property assignments do not save automatically. It returns storageKey, clear() (forget saved preferences without changing the current table), and disconnect() (stop listening). Attaching a new helper to the same table disconnects the previous one. For controlled framework state, keep your state aligned with the restored table.view and table.query instead of overwriting them on the next render.
Change the columns or sort below, then choose Recreate table or reload the page. Use defaults forgets the demo's saved preferences.
Layout and Accessibility
The component uses a native table with a caption and sortable heading buttons. Controls have accessible labels and normal keyboard navigation; the columns menu provides keyboard-operable reorder buttons. Wide tables scroll inside their region. Text wraps by default; use wrap: false to keep a value on one line, align for alignment, width for a preferred width and size="compact" for denser rows. Custom renderers remain responsible for meaningful link/button names and their own interactive content.
Pagination bounds the rendered page. Virtualisation, cell editing, spreadsheet navigation, grouping calculations, saved-view management and all-matching selection are not part of this component. A plugin can compose domain controls through slots without changing the table’s shared behaviour.
API
Slots
| Name | Description |
|---|---|
toolbar-start | Controls before search. |
toolbar-controls | Additional controls after search/filter controls and before View. |
toolbar-end | Product actions after the standard controls. |
filters | Consumer-owned criteria controls inside the filter builder; consumers own their state and application. |
selection-actions | Footer actions for the explicitly selected record IDs; space stays reserved when empty. |
footer | Additional footer information. |
empty | Replacement for the empty/no-match state. |
Attributes & Properties
| Name | Description |
|---|---|
columnControls column-controls | Type booleanDefault true |
columns | Ordered column definitions and rendering capabilities. Type DataTableColumn[]Default [] |
dataSource | Optional remote loader. Omit when the consuming application owns fetching. Type DataTableDataSource | undefined |
defaultSort | Initial/reset sort when query.sort is empty; defaults to the first sortable column ascending. Type DataTableSort[]Default [] |
defaultView | Preferences restored by Use defaults. Type DataTableViewDefault { hiddenColumns: [], columnOrder: [], } |
error | Safe user-facing error text, not a raw exception. Type string |
exportFormats | Optional export choices. The consumer handles pk-export; no files are generated. Type DataTableExportFormat[]Default [] |
exporting | Type booleanDefault false |
filters | Filter controls and local matching rules. Type DataTableFilter[]Default [] |
hasNext has-next | Type booleanDefault false |
hideFooter hide-footer | Type booleanDefault false |
hideToolbar hide-toolbar | Type booleanDefault false |
isRowSelectable | Return false to exclude a record from selection. Type ( row: DataTableRow, ) => boolean | undefined |
label | Accessible name for the table and its scroll region. Type stringDefault Records |
labels | Override any UI string; interpolation uses {id}, {label}, {count}, {start}, {end}, {total}. Type Partial<DataTableLabels>Default {} |
layout | Embedded tables flow with their content; index tables keep footer controls and horizontal scrolling in reach. Type 'embedded' | 'index'Default embedded |
loading | Type booleanDefault false |
locale | Type string |
manualOrder manual-order | Display rows in supplied order, with page-scoped pointer/keyboard reordering and no column sorting. Type booleanDefault false |
mode | Client mode processes all supplied rows. Remote mode never filters/sorts a partial page. Type 'client' | 'remote'Default client |
nextCursor next-cursor | Type string | undefined |
pagination | Type 'offset' | 'cursor'Default offset |
previousCursor previous-cursor | Type string | undefined |
query | Search, sort, criteria and pagination state. Type DataTableQuery |
reorderDisabled reorder-disabled | Disable row moves while saving or unavailable. Handles stay focusable so pending saves retain keyboard focus. Type booleanDefault false |
rowActions | Available product actions for each record; presets default to inline icon buttons. Type ( row: DataTableRow, ) => DataTableAction[] | undefined |
rowKey | A property name or function returning a unique, stable record ID. Type string | ((row: DataTableRow) => DataTableKey)Default id |
rowLabel | Optional property name or formatter for accessible row-selection labels. Defaults to the row key. Type string | ((row: DataTableRow) => string) | undefined |
rows | The complete local dataset, or one externally fetched remote page. Type DataTableRow[]Default [] |
searchable | Type booleanDefault true |
searchDelay search-delay | Type numberDefault 250 |
selectable | Type booleanDefault false |
selectedKeys | Explicit selected record IDs, retained across pages. Type DataTableKey[]Default [] |
showFilters show-filters | Type booleanDefault false |
size | Type 'default' | 'compact'Default default |
timeZone time-zone | Type string |
total | Type number | undefined |
view | Column visibility and ordering preferences. Type DataTableViewDefault { hiddenColumns: [], columnOrder: [], } |
Events
| Name | Description |
|---|---|
name | — |
pk-export | Detail: { format, query, view, columnIds, selectedKeys }. Consumer handles the export. |
pk-load | Detail: { result, query }. A current data-source response was accepted. |
pk-load-error | Detail: { error }. The current request failed. |
pk-query-change | Detail: { query }. Search, sort, criteria or page changed. |
pk-retry | A retry was requested in externally controlled remote mode. |
pk-row-action | Detail: { action, row, rowKey }. Product action requested. |
pk-row-reorder | Detail: DataTableRowReorderDetail. Current-page order changed; the consumer saves it. |
pk-selection-change | Detail: { selectedKeys }. Explicit record IDs changed. |
pk-view-change | Detail: { view }. Column visibility/order changed. |
CSS Custom Properties
| Name | Description |
|---|---|
--pk-data-table-cell-color | Inherited cell foreground, including selected state. |
--pk-data-table-cell-muted-color | Secondary cell text, including selected state. |
--pk-data-table-cell-padding | Cell spacing. |
--pk-data-table-footer-offset | Sticky footer inset from the scroll container bottom. |
--pk-data-table-header-offset | Sticky header inset from the scroll container top. |
--pk-data-table-selected-background | Selected row background. |
--pk-data-table-selected-color | Selected row foreground. |
--pk-data-table-toolbar-background | Background behind the sticky toolbar. |
CSS Parts
| Name | Description | CSS selector |
|---|---|---|
cell | Data cell. | ::part(cell) |
filter-builder | Filter condition editor. | ::part(filter-builder) |
filter-footer | Apply, cancel and clear controls. | ::part(filter-footer) |
filter-rule | Individual filter condition row. | ::part(filter-rule) |
footer | Count, selection actions and pagination region. | ::part(footer) |
footer-shell | Sticky container in index layout. | ::part(footer-shell) |
handle | Copyable monospaced handle pill. | ::part(handle) |
header-shell | Sticky toolbar in index layout. | ::part(header-shell) |
row-actions | Inline row actions and optional overflow menu. | ::part(row-actions) |
row-handle | Pointer and keyboard row-reorder button in manual-order mode. | ::part(row-handle) |
scroll | Contained horizontal scroll region. | ::part(scroll) |
scrollbar | Synchronized horizontal scrollbar in index layout. | ::part(scrollbar) |
selection | Reserved footer selection count and actions. | ::part(selection) |
table | Semantic table. | ::part(table) |
toolbar | Toolbar container. | ::part(toolbar) |
Dependencies
This component registers the following elements when it loads.
| Name | Description |
|---|---|
pk-button | — |
pk-button-group | — |
pk-checkbox | — |
pk-copy-button | — |
pk-dialog | — |
pk-dropdown-item | — |
pk-dropdown-menu | — |
pk-input | — |
pk-option | — |
pk-popover | — |
pk-select | — |
pk-spinner | — |
pk-state-panel | — |
pk-status-label | — |