A schema-driven, server-side data grid: one compound DataGrid component with a built-in toolbar, tabs, filters, sorting, column visibility, row selection, pagination and CSV export.
Reach for @uxf/data-grid when you have a paginated, filterable, server-fetched table driven by a generated schema (columns, filters, tabs). The grid owns fetching, filtering, sorting and pagination state; you provide a schema and a loader.
It is not a headless table primitive and not a spreadsheet — layout, toolbar and footer are opinionated. For a plain, fully custom table render the internal parts yourself (see Custom composition) rather than reaching for a different library.
Note: This package ships translations. Wrap your app in the
TranslationsProviderfrom@uxf/core-react/translationsfor correct labels.
yarn add @uxf/data-grid
Peer dependencies (install if not already present):
yarn add @uxf/core @uxf/core-react @uxf/localize @uxf/styles @uxf/ui \
@dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities dayjs react react-dom
Import the dependency stylesheets first, then the grid's own stylesheet last. The @uxf/ui component styles are published under the flattened @uxf/ui/css/ directory (the @uxf/ui/<component>/<file>.css source paths do not exist in the published package).
@import url("tailwindcss/components.css");
@import url("@uxf/ui/css/button.css");
@import url("@uxf/ui/css/button-list.css");
@import url("@uxf/ui/css/calendar.css");
@import url("@uxf/ui/css/checkbox.css");
@import url("@uxf/ui/css/chip.css");
@import url("@uxf/ui/css/dialog.css");
@import url("@uxf/ui/css/dropdown.css");
@import url("@uxf/ui/css/icon.css");
@import url("@uxf/ui/css/label.css");
@import url("@uxf/ui/css/form-component.css");
@import url("@uxf/ui/css/input-basic.css");
@import url("@uxf/ui/css/input.css");
@import url("@uxf/ui/css/input-with-popover.css");
@import url("@uxf/ui/css/combobox.css");
@import url("@uxf/ui/css/modal-header.css");
@import url("@uxf/ui/css/multi-select.css");
@import url("@uxf/ui/css/multi-combobox.css");
@import url("@uxf/ui/css/pagination.css");
@import url("@uxf/ui/css/select.css");
@import url("@uxf/ui/css/toggle.css");
@import url("@uxf/ui/css/tabs.css");
/* must be after the component css files */
@import url("@uxf/data-grid/styles.css");
useDataGridControl owns grid state and actions; useDataGridFetching runs the loader whenever the request changes. Feed both into DataGrid.
"use client";
import { DataGrid } from "@uxf/data-grid";
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useDataGridFetching } from "@uxf/data-grid/use-data-grid-fetching";
import { schema } from "@generated/data-grid/schema/example";
export function ExampleGrid() {
const { state, actions } = useDataGridControl({ schema });
const { isLoading, error, data, reload } = useDataGridFetching({ schema, state });
return (
<DataGrid
actions={actions}
data={data}
error={error}
isLoading={isLoading}
reload={reload}
schema={schema}
state={state}
/>
);
}
useDataGridFetching uses a default loader that calls the conventional grid endpoint. To fetch yourself, pass a loader:
const { ... } = useDataGridFetching({
schema,
state,
loader: (gridName, request, encodedRequest) => myFetch(gridName, request),
});
useUserConfigLocalStorageAdapter returns a middleware (persists column visibility, widths, order, etc. to localStorage) and a useUserConfig hook that restores it. Wire the middleware into useDataGridControl and call useUserConfig(actions).
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useUserConfigLocalStorageAdapter } from "@uxf/data-grid/user-config-storage-adapters/local-storage";
const { middleware, useUserConfig } = useUserConfigLocalStorageAdapter(schema);
const { state, actions } = useDataGridControl({
schema,
middleware,
// optional base64-encoded request or a Request object to hydrate initial state
initialState: encodedRequest,
// optional default column config
initialUserConfig: {
columns: {
id: { isHidden: true },
},
},
});
useUserConfig(actions);
Only exports listed below are part of the public surface. Import paths resolve by filesystem (there is no exports map).
@uxf/data-grid| Name | Kind | Description |
|---|---|---|
DataGrid |
component | The compound grid. Props: DataGridProps<GridType>. |
DataGridProps<GridType> |
type | Grid props = base props + control (state, actions) + data (isLoading, error, data, reload). |
InferDataGridRow<Schema> |
type | Derives the row shape from a schema via ColumnTypes. |
BaseGridType, Schema, ChangeTabFilterBehavior, DataGridActionCell, … |
types | Re-exported from ./types. |
mergeSchemaWithConfig(schema, config) |
fn | Returns a new schema with a frontend config applied (see below). |
encodeFilter(request) / decodeFilter(string) |
fn | Base64 (de)serialize a grid request. |
| Import path | Export | Description |
|---|---|---|
@uxf/data-grid/use-data-grid-control |
useDataGridControl(config) |
Owns grid state + actions. Config: { schema, initialState?, initialUserConfig?, middleware? }. |
@uxf/data-grid/use-data-grid-fetching |
useDataGridFetching(config) |
Runs the loader on request changes. Config: { schema, state, loader?, isWithTabCounts? }. Returns { isLoading, error, data, reload }. |
@uxf/data-grid/user-config-storage-adapters/local-storage |
useUserConfigLocalStorageAdapter(schema), useClearLocalStorageUserConfig(schema) |
localStorage persistence of user column config. |
@uxf/data-grid/column-types |
ColumnTypes (interface) |
Augment to register custom column value types. |
Notable optional DataGrid props: actionCell, bodyCells, changeTabFilterBehavior, customActions, filterHandlers, fulltextInputPlaceholder, getCsvDownloadUrl, hasStickyHeader, isRowsSelectable, isRowSelectDisabled, isWithTabCounts, keyExtractor, rowAccent, rowClassName, rowHeight, tabsVariant, HiddenColumnsComponent, NoRowsFallback, SelectedRowsToolbarActions, isDebug.
A column's type (from the schema) maps to a value type via the ColumnTypes interface, which drives InferDataGridRow. Built-in types:
boolean, chip, chips, date, datetime, email, id, int, money, phone, string, url, uuid.
Register a custom column type with module augmentation. Create a column-types.d.ts at the project root (next to tsconfig.json) — it must be an interface:
declare module "@uxf/data-grid/column-types" {
export interface ColumnTypes {
"my-custom-type": MyCustomType;
}
}
Then infer the row type from a schema:
import { InferDataGridRow } from "@uxf/data-grid";
import { schema } from "@generated/data-grid/schema/example";
type Row = InferDataGridRow<typeof schema>;
Each filter is rendered by looking up filterHandlers[filter.type]. The grid uses defaultFilterHandlers unless you pass a filterHandlers prop. Built-in handler keys:
filter.type |
Renders |
|---|---|
checkbox |
Checkbox (value is sent only when checked) |
date |
Date range (from / to date pickers) |
datetime |
Datetime range |
entitySelect |
Single-select combobox (async entity) |
entityMultiSelect |
Multi-select combobox (async entity) |
interval |
Numeric range (min / max inputs) |
multiSelect |
Multi-select |
select |
Select |
string |
Text input |
Extend or override by merging into defaultFilterHandlers and passing the result as the filterHandlers prop:
import { defaultFilterHandlers } from "@uxf/data-grid/filter-handler";
<DataGrid filterHandlers={{ ...defaultFilterHandlers, myType: myHandler }} {...rest} />;
mergeSchemaWithConfig returns a new schema with per-column and per-filter overrides and perPage applied — useful for tweaking a generated schema without regenerating it.
import { DataGrid } from "@uxf/data-grid";
import { mergeSchemaWithConfig } from "@uxf/data-grid";
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useDataGridFetching } from "@uxf/data-grid/use-data-grid-fetching";
import { schema as baseSchema } from "@generated/data-grid/schema/example";
const schema = mergeSchemaWithConfig(baseSchema, {
perPage: 100,
columns: {
id: { width: 100, isHidden: true },
},
filters: {
id: { placeholder: "Search by ID..." },
},
});
export function ConfiguredGrid() {
const { state, actions } = useDataGridControl({ schema });
const { isLoading, error, data, reload } = useDataGridFetching({ schema, state });
return (
<DataGrid
actions={actions}
data={data}
error={error}
isLoading={isLoading}
reload={reload}
schema={schema}
state={state}
/>
);
}
The DataGrid component is a fixed composition of internal parts. For a fully custom layout, import the parts individually and assemble them yourself, still driving them from useDataGridControl / useDataGridFetching:
@uxf/data-grid/root, @uxf/data-grid/toolbar, @uxf/data-grid/toolbar-tabs, @uxf/data-grid/toolbar-control, @uxf/data-grid/toolbar-customs, @uxf/data-grid/filter-list, @uxf/data-grid/table-v2, @uxf/data-grid/footer, @uxf/data-grid/pagination, @uxf/data-grid/row-counts, @uxf/data-grid/rows-per-page-select, @uxf/data-grid/selected-rows-toolbar, @uxf/data-grid/linear-progress, @uxf/data-grid/body-cell, @uxf/data-grid/filter-handler.
See data-grid.tsx (the default composition) and data-grid-custom-example.stories.tsx in the package for a worked example.
localStorage. Render the grid in a client component ("use client").TranslationsProvider required. Labels come from bundled translations via @uxf/core-react/translations.@uxf/data-grid/styles.css after the @uxf/ui/css/* component styles.ColumnTypes augmentation must be an interface, not a type, or the declaration merge will not apply.initialState is a base64-encoded request string (or a Request object) — use encodeFilter / decodeFilter to (de)serialize, e.g. to persist the current view in the URL.isDebug is a DataGrid prop, not a useDataGridControl config option.