---
id: "packages/active-grid/api/props"
title: "Props"
description: "GridExportFormat, GridExportOptions, GridExportRowScope, GridExportScope and 12 more: generated from the source of active-grid."
url: "/packages/active-grid/api/props"
locale: "en"
revision: "g0mvjxwj5c-88bdbe1c:props"
---

# Props

Exported by `@workspace/active-grid`. Generated from the package source; part of [the index of every export](/packages/active-grid/api).

## Types

**GridExportFormat**

```ts
type GridExportFormat = 'csv' | 'xlsx' | 'json' | 'pdf';
```

**GridExportOptions**

```ts
interface GridExportOptions {
  enabled?: boolean;
  formats?: GridExportFormat[];
  filename?: string;
  fonts?: GridExportFonts | 'inherit';
  onExportComplete?: (format: GridExportFormat, scope: GridExportScope) => void;
  onExportError?: (
    format: GridExportFormat,
    error: unknown,
    scope: GridExportScope,
  ) => void;
}
```

**GridExportRowScope**

```ts
type GridExportRowScope = 'page' | 'loaded';
```

The row set the built-in row menu can export, and which it may therefore
name HONESTLY.

Two values and no third. `page` is the current paginated grid page -- after
filtering, after sorting, within `pageIndex`/`pageSize` -- and means the
same in client and server mode, because both read the same pagination
model. `loaded` belongs to infinite scrolling, where no page exists and the
model carries all blocks fetched so far.

There is deliberately no value for "the whole set": the grid does not own
those rows, and a label promising it is then an untruth. A full dataset
export is a separate product feature.

**GridExportScope**

```ts
type GridExportScope = GridExportRowScope | 'selection';
```

The row set one export wrote: the current page or the loaded rows (the
{@link GridExportRowScope} of the grid), or the reader's selection.

**GridImperativeHandle**

```ts
interface GridImperativeHandle {
  refetch: () => void;
  resetSelection: () => void;
  addRow: (row: any) => void;
  removeRow: (rowId: string) => void;
}
```

**GridSelectionAction**

```ts
interface GridSelectionAction {
  readonly id: string;
  readonly label: string;
  readonly icon?: React.ComponentType<{className?: string}>;
  readonly tone?: GridSelectionActionTone;
  readonly separatorBefore?: boolean;
  readonly supportsDatasetWideSelection?: boolean;
  readonly onAction: (selection: SelectionModel) => void | Promise<void>;
}
```

ONE action offered on the current selection.

The grid owns the bar, the count, the fixed `Actions` menu, the keyboard
behaviour and the two presentations. The consumer owns everything that is a
PRODUCT statement: which actions exist, what they are called, what they do,
and whether the caller may perform them at all.

**Permission is absence.** An action the user may not perform is left out of
the array; the grid never renders a disabled item to stand for a missing
right. The same rule governs selection SHAPE — see
{@link GridSelectionAction.supportsDatasetWideSelection}.

**There is no `ReactNode` slot.** A free slot would let every consumer
design its own bulk bar again, which is the divergence this contract
replaces. One action renders in the same menu as five.

**GridSelectionActionTone**

```ts
type GridSelectionActionTone = 'default' | 'danger';
```

How a selection action is presented. `danger` is the destructive
presentation of `DropdownMenuItem`; there is deliberately no third tone —
a menu that colours more than "this one removes something" stops
communicating anything.

**GridToolbarConfig**

```ts
interface GridToolbarConfig<TData = DefaultRowData> {
  hidden?: boolean;
  className?: string;
  search?: {
    enabled?: boolean;
    placeholder?: string;
    className?: string;
    debounceMs?: number;
  };
  showColumnChooser?: boolean;
  actions?: React.ReactNode | ((grid: Grid<TData>) => React.ReactNode);
}
```

**SelectionModel**

```ts
type SelectionModel =
  | {mode: 'inclusive'; rowIds: string[]}
  | {
      mode: 'exclusive';
      excludedRowIds: string[];
      totalCount: number;
      query: SelectionQuery;
    };
```

Represents the current selection state, distinguishing between
inclusive mode (specific IDs selected) and exclusive mode (all selected
except specific IDs — used for server-side select-all).

**`totalCount` is for DISPLAY.** It is what the last response reported and
what the banner counts with. A mutation must never compute its own effect
from it: rows can appear or disappear between the selection and the call,
and the number arrives from the client anyway. Server-side, the count comes
from the same query that performs the work.

**SelectionQuery**

```ts
interface SelectionQuery {
  readonly filters: ReadonlyArray<SelectionQueryFilter>;
  readonly search: string;
  readonly externalKey: string | undefined;
}
```

The QUESTION an exclusive selection answers.

An exclusive selection says "everything except these ids" — and *everything*
is only meaningful together with the query that produced it. Without the
query a consumer cannot execute the selection: it either processes the rows
it happens to have loaded (silently wrong) or the whole table (dangerously
wrong). This is that query, split by OWNER:

- `filters` and `search` are what the GRID owns — its column filters and its
own toolbar search;
- `externalKey` is the `serverQueryKey` the CONSUMER supplied, i.e. the part
of the question the grid never saw.

The grid never parses any of it. It is carried, not interpreted, so a
consumer can (a) verify the selection still belongs to the scope on screen
and (b) refuse an action it cannot execute rather than execute a narrower
one. A mutation must still re-derive and re-authorize all of it server-side;
see `docs/guides/selection.md` § Executing an exclusive selection.

**SelectionQueryFilter**

```ts
interface SelectionQueryFilter {
  readonly id: string;
  readonly value: unknown;
}
```

One column filter that was active at the moment an EXCLUSIVE selection was
made. Opaque to the grid beyond its shape: `id` is the column id, `value`
is whatever that column's filter carries.

**GridPaginationOptions**

```ts
interface GridPaginationOptions {
  enabled?: boolean;
  mode?: 'paginated' | 'infinite';
  position?: 'top' | 'bottom' | 'both';
  pageSizeOptions?: number[];
  showRowCount?: boolean;
  showPageSizeSelector?: boolean;
  showPageIndicator?: boolean;
  showFirstLastButtons?: boolean;
  showPrevNextButtons?: boolean;
  rowCountPosition?: 'left' | 'right';
  className?: string;
}
```

**GridProps**

```ts
type GridProps<TData extends Record<string, unknown> | unknown> =
  RequireGetRowIdIfMissingId<GridPropsBase<TData>, TData>;
```

**LoadingDisplayOptions**

```ts
interface LoadingDisplayOptions {
  firstLoad?: LoadingDisplayValue;
  refetch?: LoadingDisplayValue;
  delayMs?: number;
}
```

Per-state loading configuration. First load and refetch are different
situations — one has an empty grid, the other has rows that just went
stale — so they are configured separately rather than through a single
mode that guesses.

**LoadingDisplayValue**

```ts
type LoadingDisplayValue = LoadingIndicator | React.ReactNode;
```

An indicator name, or your own node rendered inside the grid box.

**LoadingIndicator**

```ts
type LoadingIndicator =
  'none' | 'progress' | 'skeleton' | 'spinner' | 'text';
```

A built-in loading indicator. See {@link BaseGridProps.loadingDisplay} for
what each one looks like and when it fits.

`'skeleton'` is deliberately part of this library and is not dead code.
Datyx product apps do not use skeleton loading -- a product-level rule
enforced separately by `lint-no-skeleton-loading.mjs`, which fails any app
that opts into this value -- but active-grid is a general-purpose package
that may be consumed outside Datyx, and a product rule does not remove a
generic library feature. See `docs/api/grid-props.md` § Why `'skeleton'`
stays.
