---
title: DataTables
editUrl: https://github.com/Cratis/Components/edit/main/Documentation/DataTables/index.md
description: Render local, paged, and observable query data with sorting, filtering, and selection.
---


The DataTables module provides a semantic local-array table plus specialized Arc query and observable-query wrappers.

## Components

- **DataTableCore**: For an already-loaded local array
- **DataTableForQuery**: For standard Arc queries with pagination
- **DataTableForObservableQuery**: For observable Arc queries with real-time updates
- **ColumnFilterMenu**: The reusable draft/apply filter popup used by filterable columns
- **TablePaginator**: The standalone zero-based paginator used by query-backed tables

`DataTableCore` accepts a `status` from `DataTableStatus` (`Ready`, `Loading`, `Failed`, or `Unauthorized`) (import `DataTableStatus` from `@cratis/components/DataTables`); it defaults to `Ready`. For a loading table with no rows, it renders a status row; with rows, it retains them and marks the table busy. Failed or unauthorized states render an alert row. Override the text with `loadingMessage`, `failureMessage`, and `unauthorizedMessage` or configure `CratisComponentsProvider`'s `messages.dataTable`.

`DataTableCore` keeps its own sort, column filters, and search text unless you control them. Pass `sort` with `onSortChange`, `filters` with `onFilter`, and `globalFilter` with `onGlobalFilterChange` to own that state, for example to persist it or to send it to the server. `sort={null}` means not sorted; leaving a prop undefined lets the table keep that piece of state itself, and the change callbacks report every change either way. Import `DataTableSort`, `DataTableSortDirection`, and `DataTableRowProcessing` from `@cratis/components/DataTables`.

When the rows already reflect that state, because your query filtered and sorted them on the server, set `rowProcessing={DataTableRowProcessing.None}` so the table renders the rows as given instead of filtering and sorting them a second time. The default, `DataTableRowProcessing.Loaded`, filters and sorts the rows the table was given. The bound query tables do not take these props yet; server-side sorting and filtering for them is tracked in [#178](https://github.com/Cratis/Components/issues/178).

## When to Use

Use DataTableCore when:

- Rows are already loaded locally
- Single or multiple selection and semantic table rendering are sufficient
- Filtering and sorting should apply only to that loaded array
- You run the query yourself and need a custom layout for its states beyond the query tables' loading and failure rows (see [Loading, empty, and failed queries](/components/datatables/data-table-for-query/#loading-empty-and-failed-queries))

Use DataTableForQuery when:

- You have standard query results
- You need server-side pagination
- Data doesn't need real-time updates

Use DataTableForObservableQuery when:

- You need real-time data synchronization
- Data changes frequently
- You want automatic UI updates

## Common Features

All three table components share:

- Single-row selection by default, or multiple selection with row checkboxes and a select-all header (`selectionMode='multiple'`)
- Global filtering
- Custom column templates
- Empty, loading, failure, and unauthorized state messages (set `DataTableCore.status` when rendering a locally managed query)
- Keyboard selection: each row is a tab stop, and Enter or Space selects it; in multiple mode it toggles the row (on `DataTableCore`, only when `selectionMode` or `onRowClick` is set)
- Cratis-owned semantic Column markers and stable table parts

Keep an application-owned or direct toolkit table when the surface requires grouping, row expansion, or controlled lazy/server sorting. Components does not silently emulate those behaviors over one loaded page.

## ColumnFilterMenu

`ColumnFilterMenu` is the same Cratis-owned filter trigger and draft/apply popup that `Column` uses when `filter` is enabled. Most tables should configure filtering through `Column`; use the component directly only when composing a custom table header or another filtering surface.

```tsx
import { useState } from 'react';
import {
    ColumnFilterMenu,
    type DataTableFilterConstraint,
} from '@cratis/components/DataTables';

export function TotalFilter() {
    const [constraint, setConstraint] = useState<DataTableFilterConstraint>();

    return (
        <ColumnFilterMenu
            field='total'
            dataType='numeric'
            constraint={constraint}
            onApply={setConstraint}
            onClear={() => setConstraint(undefined)}
        />
    );
}
```

| Prop | Type | Description |
| --- | --- | --- |
| `field` | `string` | Effective field being filtered; also supplies default accessible labels. |
| `dataType` | `'text' \| 'numeric' \| 'date' \| 'boolean'` | Selects the built-in editor and match-mode family. Defaults to `'text'`. |
| `placeholder` | `string` | Placeholder for the built-in value editor. |
| `showMatchModes` | `boolean` | Shows the match-mode selector. Defaults to `true`. |
| `filterOptions` | `ColumnFilterOption[]` | Offers a known set of `{ label, value }` choices in a dropdown instead of a typed value, and narrows the match modes to Equals and Not equals. Takes precedence over `filterElement`. |
| `filterElement` | `ColumnFilterElement` | Replaces the built-in editor. Its options expose draft value/mode updates and apply/clear actions. |
| `labels` | `Partial<ColumnFilterMenuLabels>` | Localizes trigger, editor, match mode, and action labels. Provider-level defaults are also honored. |
| `pt` | `ColumnFilterMenuParts` | Stable part attributes for the trigger, popover, menu, editor, actions, and action buttons. |
| `constraint` | `DataTableFilterConstraint` | Currently applied value and optional match mode. |
| `onApply` | `(constraint: DataTableFilterConstraint) => void` | Receives the draft constraint when Apply is activated. |
| `onClear` | `() => void` | Removes the field constraint. |

The popup keeps draft edits local until Apply is activated. Clear resets the draft value and calls `onClear`; Apply calls `onApply`. Neither action closes the popup automatically—it remains open until the user dismisses it or toggles the trigger. Built-in date, numeric, boolean, and text editors use the corresponding match modes; register custom matching behavior through the DataTables matcher APIs rather than relying on a renderer-specific registry.

## TablePaginator

Use `TablePaginator` when a custom query/list composition needs the same first/previous/next/last navigation as the query-backed tables. Pages are always zero-based.

```tsx
import { TablePaginator } from '@cratis/components/DataTables';

<TablePaginator
    page={1}
    pageCount={8}
    pageSize={10}
    totalItems={75}
    onPageChange={(page) => loadPage(page)}
/>;
```

| Prop | Type | Description |
| --- | --- | --- |
| `page` | `number` | Current zero-based page. |
| `pageCount` | `number` | Total page count. Boundary controls disable on the first and last pages. |
| `onPageChange` | `(page: number) => void` | Receives the requested zero-based page. The host owns loading and current-page state. |
| `totalItems` | `number` | Total record count. Together with `pageSize`, enables the loaded range report. |
| `pageSize` | `number` | Rows per page. Together with `totalItems`, enables the loaded range report. |
| `ariaLabels` | `object` | Localizes the navigation and first/previous/next/last accessible names. |
| `className` | `string` | Extra class on the paginator root. |
| `pt` | `TablePaginatorParts` | Stable attributes for the root, reports, and the four button parts. |

`TablePaginator` does not fetch data or change pages internally. Keep `page`, `pageCount`, and the optional range inputs synchronized with the query result supplied by the host.

## See Also

- [Choosing a component](/components/choosing-a-component/) - Local, query-backed, and advanced table boundaries
- [DataTableForQuery](/components/datatables/data-table-for-query/) - Standard query tables
- [DataTableForObservableQuery](/components/datatables/data-table-for-observable-query/) - Real-time tables
- [Column Configuration](/components/datatables/column-configuration/) - Customizing columns
