# DataGrid

Strictly typed server-driven tables, with page and cursor navigation, native Query options, and remote selection.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





## Playground [#playground]

```tsx
"use client";

import { QueryClient, QueryClientProvider, queryOptions } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { z } from "zod";
import { createDataGridContract } from "@/components/data-grid/schema";
import { createDataGridColumnHelper, useDataGrid } from "@/components/data-grid";
import { DataGrid, DataGridToolbar, DataGridSearch, DataGridApplyFilters, DataGridResetFilters, DataGridColumnVisibility, DataGridSelectionBar, DataGridViewport, DataGridTable, DataGridPagination } from "@/components/data-grid";

const contract = createDataGridContract({
  pagination: "page",
  row: z.object({ id: z.string(), name: z.string() }),
  filters: z.object({ search: z.string().default("") }),
  sortBy: z.literal("name"),
});
const column = createDataGridColumnHelper(contract);
const columns = [column.accessor("name", { header: "Name", sortBy: "name" })];
const records = Array.from({ length: 78 }, (_, index) => ({ id: String(index), name: "User " + (index + 1) }));

function Users() {
  const grid = useDataGrid({
    contract, columns, getRowId: row => row.id,
    filterMode: "immediate", selectionMode: "none",
    queryOptions: input => queryOptions({
      queryKey: ["users-demo", "ready", input],
      retry: false, enabled: true,
      queryFn: async ({ signal }) => {
        // In-memory backend simulation. Replace with an authorized transport.
        await new Promise<void>((resolve, reject) => {
          if (signal.aborted) { reject(new DOMException("Cancelled", "AbortError")); return; }
          const abort = () => { clearTimeout(timer); reject(new DOMException("Cancelled", "AbortError")); };
          const timer = setTimeout(() => { signal.removeEventListener("abort", abort); resolve(); }, 120);
          signal.addEventListener("abort", abort, { once: true });
        });
        const rows = records.filter(row => row.name.toLowerCase().includes(input.filters.search.toLowerCase()));
        rows.sort((a, b) => {
          const direction = input.sorting[0]?.desc ? -1 : 1;
          return input.sorting.length ? direction * a.name.localeCompare(b.name) || Number(a.id) - Number(b.id) : Number(a.id) - Number(b.id);
        });
        const start = input.pagination.pageIndex * input.pagination.pageSize;
        const end = start + input.pagination.pageSize;
        return contract.output.parse({ rows: rows.slice(start, end), rowCount: rows.length });
      },
    }),
  });
  return <DataGrid grid={grid} variant="default" density="comfortable">
    <DataGridToolbar>
      <DataGridSearch grid={grid} field="search" clearable aria-label="Search users" placeholder="Search users" />
      <DataGridResetFilters /><DataGridColumnVisibility />
    </DataGridToolbar>
    <DataGridSelectionBar />
    <DataGridViewport className="max-h-80">
      <DataGridTable grid={grid} aria-label="Users" stickyHeader={false} />
    </DataGridViewport>
    <DataGridPagination showPageNumbers={true} />
  </DataGrid>;
}
export default function UsersDemo() {
  const [client] = useState(() => new QueryClient({ defaultOptions: { queries: { gcTime: Infinity } } }));
  useEffect(() => () => client.clear(), [client]);
  return <QueryClientProvider client={client}><Users /></QueryClientProvider>;
}

```

## Installation [#installation]

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

```bash
# From the Vandor UI checkout, after initializing shadcn in the consumer:
pnpm registry:build
node scripts/install-component-family.cjs data-grid /absolute/path/to/consumer
```

The default installation target is components/data-grid/data-grid.tsx. Review the CLI's overwrite prompt before replacing local customizations.

## Dependencies [#dependencies]

Installed automatically by the registry CLI. For manual setup:

```bash
npm install @tanstack/react-table@^9.2.6 @tanstack/react-query@^5.104.1 zod@^4.3.6 cn lucide-react
```

## Usage [#usage]

Supply an existing `QueryClientProvider`. Define the domain once with Zod, derive columns from it, and pass native Query options to `useDataGrid`. No duplicate wire interfaces or repeated generic arguments are needed.

<ComponentPreview name="data-grid-demo" title="data-grid-demo.tsx">
  <DataGridDemo />
</ComponentPreview>

Examples use an asynchronous in-memory backend simulation, not a database. It processes filters, deterministic sorting, and only the requested page. Replace the service with an authorized transport. Query owns remote data, cancellation, retries, and cache freshness; DataGrid never copies rows into another cache or replaces your `select`, keys, `enabled`, or placeholder policy.

### Consumer-owned data without a Query provider [#consumer-owned-data-without-a-query-provider]

<ComponentPreview name="data-grid-source-demo" title="data-grid-source-demo.tsx">
  <DataGridSourceDemo />
</ComponentPreview>

Import `useDataGridSource` from the installed `@/components/data-grid` entry. It shares the request, filter, table, pagination and selection controller with `useDataGrid`. Pass the same `contract`, `columns`, `getRowId`, and optional controller state/callbacks, plus `source: { data, error?, loading?, refreshing?, refetch }`. `data` is the contract output or `undefined`; `refetch` is required. No fetch, cache, or QueryClient provider is created by this adapter. Existing package installation dependencies remain unchanged.

The example owns a controlled request and slices deterministic local records. Use **Complete request** or **Fail request** to finish the initial load, then **Refresh source** to retain rows during refresh; **Try again** calls the source's refetch callback. **Restart demo** restores the initial request and selection. These are explicit local transitions, not network requests or a transport implementation.

For a remote source, the consumer owns authorization, caching, cancellation, and correspondence between data and the current request, including stale-response arbitration. Do not pass another request's cached rows as current data. With no data, `loading` means initial loading, an error means error, and no activity means inactive. With data, `refreshing`/`loading` retains rows with refreshing feedback, error retains rows with background-error feedback, and zero rows means empty. Cursor history commits successful current output only. `grid.query` on this path exposes only `data`, `error`, and `refetch`, not a fabricated native Query result.

`useDataGrid` continues to expose the real native Query result and preserve `select` inference. Both adapters keep selection across pagination/sorting and clear it when effective filters change. The `pageSizes` selector is hidden for zero or one choice, without hiding range announcements or navigation.

### Consumer setup [#consumer-setup]

Requires React 19-compatible Vandor primitives, Tailwind v4, Vandor/shadcn theme tokens (`background`, `foreground`, `muted`, `accent`, `border`, `ring`, `destructive`), and the existing primitives' animation setup. Verified integration uses Table 9.2.6, Query 5.104.1, and Zod 4.3.6. Backend-only schema installation has no React runtime imports:

```bash
npx shadcn@latest add https://vandor-ui.vercel.app/r/data-grid-schema.json
```

Use CLI installation for the complete UI. Copying the Source block alone is insufficient: DataGrid also needs `data-grid-columns`, `data-grid-context`, `data-grid-controls`, `data-grid-controller`, `data-grid-feedback`, `data-grid-labels`, `data-grid-selection`, `data-grid-state`, `use-data-grid`, `use-data-grid-source`, `data-grid-schema`, and the shared `error-state-base` module, plus Button, Checkbox, Select, Dropdown, InputSearch, Empty and their dependencies. Review overwrite prompts for existing customized primitives. Supporting files share identical content with their owning registry items.

### Backend reuse [#backend-reuse]

`createDataGridContract` exposes normal `.input`, `.output`, `.row`, `.filters`, `.pagination`, and `.sorting` Zod schemas. Reuse them in REST/server functions or tRPC:

```ts
const listUsers = procedure
  .input(usersGrid.input)
  .output(usersGrid.output)
  .query(({ input, ctx }) => ctx.users.listAuthorized(input));
```

```tsx
const grid = useDataGrid({
  contract: usersGrid,
  columns,
  getRowId: (row) => row.id,
  queryOptions: (input) => trpc.users.list.queryOptions(input),
});
```

These are integration fragments; create the router/provider in your application. The native query key must include all result-affecting request fields and authorization/tenant scope. DataGrid does not rewrite tRPC keys or prove key completeness. Explicitly validate transport outputs at the transport boundary, not through a hidden grid `select`.

Contracts default to 25 rows, maximum 100, empty sorting, and null initial cursors. Builders accept `pageSize`/`maxPageSize`. Page inputs use zero-based `pageIndex`; output `{ rows, rowCount }` counts all filtered results. Cursor outputs use `{ rows, nextCursor }`, with opaque strings or null. Sort keys are independently allowlisted; duplicate keys and unsafe integer bounds are rejected.

### Raw drafts and transforms [#raw-drafts-and-transforms]

Draft bindings infer `z.input`, while `request` and query factories use normalized `z.output`. Changes parse raw filters once; pagination never parses applied filters again. The initial raw baseline is retained. Restoring externally controlled normalized filters uses Zod encode: transformed filters requiring this inverse must use reversible Zod v4 codecs. A one-way transform needing restoration throws a clear diagnostic rather than applying the transform twice. Row/transport transforms are unaffected.

## Cursor pagination [#cursor-pagination]

Page pagination shows Previous/Next icons and numbered links by default, for example `‹ 1 2 3 … 8 ›`. First and last numbers remain reachable; the active number uses `aria-current="page"`. Set `<DataGridPagination showPageNumbers={false} />` for icon-only First/Previous/Next/Last controls with a page indicator instead. Icons retain localized accessible names. While totals are unavailable or placeholder rows belong to another request, numbered links are not inferred from stale metadata.

<ComponentPreview name="data-grid-cursor-demo" title="data-grid-cursor-demo.tsx">
  <DataGridCursorDemo />
</ComponentPreview>

Next uses current successful response metadata. Previous uses successfully visited cursors, not cached rows. Failed requests remain retryable and do not append history. Filtering, sorting, and page-size changes reset history. There is no arbitrary jump, total, or last-page action. A remount/deep link starts fresh local history.

## Filters and Apply [#filters-and-apply]

<ComponentPreview name="data-grid-filters-demo" title="data-grid-filters-demo.tsx">
  <DataGridFiltersDemo />
</ComponentPreview>

Immediate mode validates each changed field, with Search debounced 300 ms by default. Invalid values remain visible for correction without issuing a request. Independent debounces merge into the current applied filters; clear/reset cancels pending timers. Apply mode edits a whole draft and commits immediately when Apply is clicked, without waiting for debounce. Reset restores the captured initial baseline and resets pagination. Field reset restores only that field.

`DataGridFilter` and `DataGridSearch` take an explicit typed `grid` prop because JSX parent context cannot infer domain field/value types. The render binding supplies `value`, `onChange`, `reset`, and `error`; it accepts arbitrary controls. The date example maps DateRangePicker's committed dates to local `yyyy-MM-dd` calendar strings. Calendar Cancel does not change the grid draft. Calendar Apply changes the draft; grid Apply sends the group. Pick date-only versus timestamp semantics explicitly; there is no implicit UTC conversion.

## Remote selection [#remote-selection]

<ComponentPreview name="data-grid-selection-demo" title="data-grid-selection-demo.tsx">
  <DataGridSelectionDemo />
</ComponentPreview>

Selection defaults to none. Explicit IDs survive paging and sorting. The header checkbox toggles only the visible eligible page and supports indeterminate state. Opt-in all-matching selection emits `{ mode: "allMatching", filters, excludedIds }`, not a million-ID list. Deselected rows/pages become exclusions. Effective applied-filter changes clear selection; draft edits do not. Placeholder rows cannot be selected, and Clear remains available.

Validate descriptors with `createDataGridSelectionSchema({ filters, id })`. UI IDs are stable unique strings; selection schemas deduplicate IDs/exclusions. All-matching means matching at action execution time, not a frozen dataset. Backend authorization, tenant boundaries, eligibility, destructive confirmation, and exact-time snapshot tokens remain application responsibilities. Totals are informational; changed/deleted exclusions can make an exact selected count unknowable. Do not submit only Table's loaded selected rows as the full remote selection.

## Controlled state [#controlled-state]

<ComponentPreview name="data-grid-controlled-demo" title="data-grid-controlled-demo.tsx">
  <DataGridControlledDemo />
</ComponentPreview>

Control slices independently through `state` and matching `onStateChange` callbacks: `request`, `columnVisibility`, `columnOrder`, `selection`. Request contains pagination, applied filters, and sorting as one atomic value. A supplied slice is authoritative; callbacks propose values without competing internal writes. Parent updates actually apply controlled requests. External filter restoration rebases raw drafts and cancels pending edits. `initialState` is read once and is the reset baseline, not a controlled prop.

URL/router adapters remain application-owned. Remount with a scope key when tenant/resource identity changes to clear incompatible draft, selection, and cursor history. Never carry placeholder data across an authorization scope.

## Rendering and columns [#rendering-and-columns]

<ComponentPreview name="data-grid-rendering-demo" title="data-grid-rendering-demo.tsx">
  <DataGridRenderingDemo />
</ComponentPreview>

Column definitions are the single configuration source. Key accessors infer the field value; computed accessors need a stable ID and infer their result; display columns need an ID but no accessor. `cell({ value, row, native })` receives domain data and the real Table context. Backend `sortBy`, column ID, label, and accessor are independent. Non-text headers require a text `label`. Multiple columns sharing a backend sort key do not produce duplicate request sort entries.

Array order initializes display order. Restored `columnOrder` ignores obsolete IDs and appends missing known IDs; non-hideable columns stay visible. Default cells show strings/numbers directly and a placeholder for null/undefined/complex values; explicitly format currency, dates, and objects.

`renderColumn`, `renderRow`, and `renderCell` receive `grid`, native context, and spreadable primitive props; row/cell overrides also receive the inferred domain row. Heterogeneous Table-level cell values are unknown; use typed column cell callbacks for value-specific rendering. Spread `context.props` to retain default children, selection/sorting controls, and semantics. Replacing children or an entire primitive makes native/ref/event responsibility yours. Use accessible links/buttons for navigation, not click-only rows.

Root owns variant/density/labels. Column, Row, and Cell are native `th`/`tr`/`td` primitives, not JSX registration effects. Table supplies overflow by default; an explicit Viewport becomes the sole scroll container. Sticky headers stay within that viewport. Toolbar, pagination, and selection bar are optional presentation.

## Behavior & Accessibility [#behavior--accessibility]

* Server processing only: returned backend order is retained, without client-side filtering/sorting/pagination factories. A filter/sort/page-size change resets pagination in one request. Three-state sorting cycles ascending, descending, unsorted; Shift adds multi-sort priority.
* Current successful out-of-range pages correct once to the last valid index. Zero results display page one and `0–0`, never negative ranges. Placeholder metadata never corrects or navigates another request.
* Initial active requests show skeleton rows. Successful empty results use Empty, with reset for non-baseline filters. Initial/request-key errors use centered ErrorState with retry; same-key background errors retain rows with inline feedback. Raw backend messages/stacks are not rendered by default.
* Loading, refreshing, updating (placeholder), and paused-with-rows announcements are screen-reader-only; they add no visible text row or status badge above the table. Loading/refreshing/updating keep `aria-busy` on the table. Disabled queries show neutral inactive feedback; paused requests without rows show waiting feedback instead of an endless active skeleton. Query cancellation/race policy remains native.
* Native table semantics, keyboard-accessible sort buttons, `aria-sort`, labeled checkboxes, visible focus and live async/selection feedback are preserved. Add an accessible table name or caption. Custom filters must associate visible labels/help/errors with their controls.
* Localize all visible/accessible strings through root `labels`; `sort`, `selectRow`, `page`, `range`, `selected`, and `selectAllMatching` are formatter functions. Avoid parsing English strings for localization. Toolbars wrap on narrow screens; wide tables scroll within their viewport.
* Controller exposes native `query`/`table`, `request`, raw `filterDraft`, field/form errors, selection/preferences/presentation and typed actions: `setFilter`, `setFilters`, `resetFilter`, `applyFilters`, `resetFilters`, `setSorting`, `setPageSize`, `nextPage`, `previousPage`, `toggleRow`, `togglePage`, `selectAllMatching`, `clearSelection`. `setPageIndex` exists only on page grids. `getFilterBinding` returns the typed raw field binding.

### Optional portable stories [#optional-portable-stories]

Install DataGrid first, then add stories separately:

```bash
# From the Vandor UI checkout, with shadcn initialized in the consumer:
pnpm registry:build
node scripts/install-component-family.cjs data-grid-stories /absolute/path/to/consumer
```

The wrapper resolves the consumer's configured components alias; stock shadcn 4.5.0 does not support every nested alias. Stories do not reinstall core or modify `.storybook`. Use an existing React Storybook with matching package versions, `@storybook/react` types, and a story glob covering your configured components directory (`**/*.stories.tsx`). Types target the installed 10.6.1 version; use matching versions in your setup. Inherit your global Tailwind/theme CSS and decorators. Query-backed stories include their own QueryClientProvider and simulated service; ConsumerSource needs no provider and supports Retry recovery. There are no website imports or extra addon requirements. Storybook UI is not verified: this project has no Storybook server.

## Props [#props]

| Prop            | Type                                                       | Default                             | Description                                                                                                                                                                                                                                       |
| --------------- | ---------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| columns         | readonly DataGridColumnDefinition\[]                       | Required                            | Both adapters: typed helper definitions, independent column ID/accessor/backend sort key.                                                                                                                                                         |
| debounce        | number                                                     | 300                                 | DataGridSearch: immediate-mode delay in milliseconds; clear applies immediately. Apply mode ignores delay.                                                                                                                                        |
| density         | "comfortable" \| "compact"                                 | "comfortable"                       | DataGrid: cell padding.                                                                                                                                                                                                                           |
| field           | Inferred raw filter key                                    | Required                            | DataGridSearch / DataGridFilter: explicitly supply grid for field/value inference. Search accepts string-compatible fields only.                                                                                                                  |
| filterMode      | "immediate" \| "apply"                                     | "immediate"                         | Both adapters: validate immediately or commit the whole draft.                                                                                                                                                                                    |
| getRowId        | (row) => string                                            | Required                            | Both adapters: stable unique domain IDs, never page indexes.                                                                                                                                                                                      |
| grid            | Inferred controller                                        | Required                            | DataGrid and typed Table/Search/Filter overrides: controller from useDataGrid or useDataGridSource.                                                                                                                                               |
| initialState    | Partial\<DataGridRawInput>                                 | Schema defaults                     | Both adapters: captured once as the uncontrolled initialization/reset baseline.                                                                                                                                                                   |
| labels          | Partial\<DataGridLabels>                                   | English defaults                    | DataGrid: visible and accessible labels; count/sort messages are formatter functions.                                                                                                                                                             |
| onStateChange   | Per-slice callbacks                                        | Not set                             | Both adapters: emits the proposed value; controlled changes apply only after the parent supplies it.                                                                                                                                              |
| pageSizes       | readonly number\[]                                         | \[25, 50, 100]                      | DataGridPagination: sizes must fit contract limits; zero/one choices hide only the size selector. Cursor mode does not show total/first/last controls.                                                                                            |
| pagination      | "page" \| "cursor"                                         | Required contract choice            | Contract: numbered pages or visited cursor navigation.                                                                                                                                                                                            |
| queryOptions    | Native query-options factory                               | Required                            | useDataGrid: input is normalized; native selected output matches the contract. Query owns all remote data.                                                                                                                                        |
| renderCell      | (context) => ReactNode                                     | DataGridCell                        | DataGridTable: domain row, native cell, unknown heterogeneous value and spreadable td props. Typed values belong in column cell callbacks.                                                                                                        |
| renderColumn    | (context) => ReactNode                                     | DataGridColumn                      | DataGridTable: native header, grid and spreadable th props including sorting controls.                                                                                                                                                            |
| renderEmpty     | (\{ grid, filtered, reset }) => ReactNode                  | Empty composition                   | DataGridTable: feedback content inside a valid spanning cell.                                                                                                                                                                                     |
| renderError     | (\{ grid, error, retry, background }) => ReactNode         | ErrorState composition              | DataGridTable: raw errors reach custom handlers only, never default feedback text.                                                                                                                                                                |
| renderRow       | (context) => ReactNode                                     | DataGridRow                         | DataGridTable: inferred domain row, native row, grid and spreadable default tr props.                                                                                                                                                             |
| scenario        | "ready" \| "loading" \| "empty" \| "error" \| "inactive"   | "ready"                             | Demo-only async simulation, not a DataGrid prop.                                                                                                                                                                                                  |
| selectionMode   | "none" \| "explicit" \| "allMatching"                      | "none"                              | Both adapters: remote ID selection policy.                                                                                                                                                                                                        |
| showPageNumbers | boolean                                                    | true                                | DataGridPagination: page links with ellipses instead of First/Last. Set false for four icon controls. Cursor mode always uses Previous/Next only.                                                                                                 |
| source          | DataGridSource\<C>                                         | Required for useDataGridSource only | useDataGridSource: contract output or undefined, optional error/loading/refreshing, and refetch callback. Consumer owns transport/cache and current-request correspondence. No Query provider is required; query exposes only data/error/refetch. |
| state           | \{ request?, columnVisibility?, columnOrder?, selection? } | Uncontrolled                        | Both adapters: supplied slices are authoritative. Request contains filters/sorting/pagination atomically.                                                                                                                                         |
| stickyHeader    | boolean                                                    | false                               | DataGridTable: sticky header within the sole viewport.                                                                                                                                                                                            |
| variant         | "default" \| "striped" \| "bordered" \| "plain"            | "default"                           | DataGrid: visual treatment only.                                                                                                                                                                                                                  |

Root/Viewport/Table/Column/Row/Cell forward their corresponding native element attributes, refs and handlers. Search forwards InputSearch props except controlled value/defaultValue. Filter is a typed render binding, not a DOM element. ColumnVisibility has no native-prop API; Pagination/SelectionBar/Toolbar forward div props. Apply/Reset forward Button props.

## Source [#source]

```tsx
import { cn } from "cn";
import type * as React from "react";

type ErrorStateProps = React.ComponentProps<"div"> & {
  border?: "none" | "solid" | "dashed";
  variant?: "centered" | "inline";
};

const ErrorState = ({
  className,
  border = "none",
  variant = "centered",
  ...props
}: ErrorStateProps) => (
  <div
    data-slot="error-state"
    data-variant={variant}
    data-border={border}
    className={cn(
      "group/error-state flex w-full min-w-0 gap-4 rounded-xl text-foreground",
      border === "solid" && "border border-solid",
      border === "dashed" && "border border-dashed",
      variant === "inline"
        ? "items-start p-4 text-start"
        : "flex-col items-center justify-center p-6 text-center",
      className
    )}
    {...props}
  />
);

const ErrorStateMedia = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-media"
    className={cn(
      "flex size-9 shrink-0 items-center justify-center rounded-lg bg-destructive/10 text-destructive [&_svg]:size-5 [&_svg]:shrink-0",
      className
    )}
    {...props}
  />
);

const ErrorStateContent = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-content"
    className={cn(
      "flex w-full min-w-0 max-w-sm flex-col gap-4 group-data-[variant=inline]/error-state:max-w-none",
      className
    )}
    {...props}
  />
);

const ErrorStateHeader = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-header"
    className={cn("flex min-w-0 flex-col gap-2", className)}
    {...props}
  />
);

const ErrorStateTitle = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-title"
    className={cn(
      "text-sm font-medium tracking-tight wrap-anywhere text-balance",
      className
    )}
    {...props}
  />
);

const ErrorStateDescription = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-description"
    className={cn(
      "text-sm/relaxed text-muted-foreground wrap-anywhere",
      className
    )}
    {...props}
  />
);

const ErrorStateActions = ({
  className,
  ...props
}: React.ComponentProps<"div">) => (
  <div
    data-slot="error-state-actions"
    className={cn(
      "flex min-w-0 flex-wrap items-center justify-center gap-2 group-data-[variant=inline]/error-state:justify-start",
      className
    )}
    {...props}
  />
);

export {
  ErrorState,
  ErrorStateActions,
  ErrorStateContent,
  ErrorStateDescription,
  ErrorStateHeader,
  ErrorStateMedia,
  ErrorStateTitle,
};
export type { ErrorStateProps };

```
