@azrico/reactgrid
Private React data grid package for Azrideus' projects.
Usage
import { AzGrid } from '@azrico/reactgrid';
import '@azrico/reactgrid/dist/src/themes/azgrid-theme.css'; // optional colours
const columns = [{ name: 'name', header: 'Name', defaultFlex: 1 }];
const rows = [{ __table_id: '1', name: 'Alice' }];
<AzGrid theme="light" columns={columns} dataSource={rows} />;
The component imports grid.css (layout and structure, no colours) itself. azgrid-theme.css is the optional
token preset: it paints every element from the --azgrid-* variables below with built-in light and dark fallbacks.
Without it the grid renders unpainted and inherits the page's colours, so you can ship your own stylesheet against the
stable azgrid__* class names instead.
Peer dependencies: react, @tanstack/react-table (>= 8.20) and @tanstack/react-virtual (>= 3.10).
Upgrading from 1.x? Read MIGRATION-2.0.md: gridOptions is gone and every option is a top-level prop.
Props
Every x / defaultX / onXChange trio works controlled (pass x) or uncontrolled (pass defaultX).
Data
| Prop | Type | Description |
|---|---|---|
dataSource | any[] | { data, count? } | (params) => rows | Promise | Rows, or a loader called with { skip, limit, filterValue, sortInfo }. A loader returns rows or { data, count }. |
loading | boolean | Shows the load mask. |
looseCacheCheck | boolean | Ignore the dataSource identity when deciding whether the cache is still valid. |
pagination | false | 'local' | 'remote' | remote asks the dataSource for each page, local pages the loaded rows. |
limit / defaultLimit / onLimitChange | number | Page size. |
skip / defaultSkip / onSkipChange | number | Offset of the current page. |
pageSizes | number[] | Choices of the page size select. |
remoteFilter / remoteSort | boolean | Send the filters or the sort to the dataSource instead of applying them to the loaded rows. |
Columns
| Prop | Type | Description |
|---|---|---|
columns | (LooseColumnDefinition | string)[] | Column definitions, see Column definition. |
columnExtensions | LooseColumnDefinition[] | Merged into the column of the same name, existing values win. |
customColumns | { name, render }[] | Replaces the render of the column of the same name. |
editableColumns | string[] | Names of the columns made editable. |
leftColumns / rightColumns | string[] | Columns pinned to the start or end edge. |
clipScrolledCells | boolean | Cuts scrolled cells where pinned columns cover them, instead of painting the pinned cells opaque, so pinned cells can keep a translucent background. Default false. |
gridId | string | Saves resized widths, see Saved column widths. |
resizable / sortable | boolean | Defaults for columns that don't set their own (default true). |
filterable | boolean | Shows the filter row. Default true when a column is filterable or a filter value is given. |
onColumnResize | ({ column, width }) => void | Called when a resize ends. |
onColumnVisibleChange | ({ column, visible }) => void | Called when the column menu hides or shows a column. |
columnMenu | boolean | Header menu with sort and show/hide entries (default true). |
columnMenuItems | (column, api) => menu items | Entries appended to the header menu of every column. |
Sort and filter
| Prop | Type | Description |
|---|---|---|
sortInfo / defaultSortInfo / onSortInfoChange | { name, dir: 1 | -1 } | null | Single-column sort. Header clicks cycle ascending, descending, none. |
filterValue / defaultFilterValue / onFilterValueChange | { name, operator, type, value }[] | Filter row values. |
filterTypes | { [type]: { operators: { name, fn }[] } } | Merged over the built-in filter types. fn({ value, filterValue, data }) returns a boolean. |
filterDelay | number | Ms to wait after typing in a filter before it applies (default 250). |
advancedFilterFields | string[] | Columns searched by the quick-search box above the grid. |
advancedFilterLabel / advancedFilterPlaceholder | ReactNode | Label and placeholder of the quick-search box. |
Built-in operators: string contains, eq, neq, startsWith, endsWith; number eq, neq, gt, gte, lt, lte, inrange; select eq, inlist; bool eq.
Rows
| Prop | Type | Description |
|---|---|---|
rowHeight | number | ({ data, rowIndex }) => number | Row height in px (default 35). The function form sets each row's height. |
headerHeight | number | Header height in px. |
rowClassName / rowStyle | value or (rowProps) => value | Classes and style of a row. |
onRenderRow | (rowProps) => void | Called for every rendered row. Keys you add to rowProps become DOM attributes. |
showZebraRows / showHoverRows | boolean | Striped rows and hover paint. |
emptyText | ReactNode | Shown when there are no rows. |
footer | { render: { [column]: node }, colspan?: { [column]: n } } | One footer row that sticks to the bottom. |
highlightedRow | row or row id | Row drawn with the active style. |
onRowReorder | ({ rows, data, fromIndex, toIndex }) => void | Lets the user reorder rows (see below). |
Row reorder
With onRowReorder the user can drag a row to a new position with the mouse, or move the active row with
Alt+ArrowUp / Alt+ArrowDown. The grid doesn't change its data: rows is a new array of the loaded top-level rows in
their new order (the same row objects), pass it back as the dataSource. data is the moved row, fromIndex and
toIndex its index in the loaded rows before and after the move.
const [rows, setRows] = React.useState(initialRows);
<AzGrid columns={columns} dataSource={rows} onRowReorder={({ rows }) => setRows(rows)} />;
Reordering is off while the rows are sorted (the shown order isn't the data order), with allowChildRows and with
pagination: 'remote'. A row with an open cell editor can't be dragged. Dragging uses native HTML5 drag and drop,
so it works with a mouse, not on touch screens. A dragged row lands after a row below it and before a row above it.
While dragging, the row gets azgrid__row--dragging and the row under the pointer azgrid__row--drop-after or
--drop-before, drawn as a line in --azgrid-accent.
To fit n rows without a scrollbar, give the grid get_grid_height(n, rowHeight, headerHeight) px of height: headerHeight + 1 + n * rowHeight + 2 (the header's 1px bottom border and the grid's 1px top and bottom border of the default theme). Add the filter row, footer and pagination bar when they are shown.
Tree and details
| Prop | Type | Description |
|---|---|---|
allowChildRows | boolean | Rows with __children (or __parent__table_id) render as a tree. |
expandedNodes / defaultExpandedNodes / onExpandedNodesChange | { [rowId]: boolean } | Expanded tree nodes. |
RowDetail | ({ data, rowIndex, rowId, toggle }) => ReactNode | Rendered under an expanded row. Adds an expand column. |
rowExpandHeight | number | ({ data }) => number | Height of the details block. |
expandedRows / defaultExpandedRows / onExpandedRowsChange | { [rowId]: boolean } | Rows whose details are open. |
Selection
| Prop | Type | Description |
|---|---|---|
selected / defaultSelected / onSelectionChange | true | { [rowId]: row } | null | Selected rows. true means all rows. |
multiSelect | boolean | The checkbox and its cell toggle the row, shift click selects a range. |
toggleSelectionOnRowClick | boolean | A click anywhere on a row toggles it like its checkbox. Without it a row click only makes the row active and doesn't change the selection. |
checkboxColumn | boolean | Adds a checkbox column with a select-all header. |
checkboxOnlyRowSelect | boolean | Only the checkbox selects, a row click doesn't. |
rowIndexColumn | boolean | Adds a row number column. |
cellSelection / defaultCellSelection / onCellSelectionChange | { "rowId,column": true } | Cell selection. |
Editing
| Prop | Type | Description |
|---|---|---|
editable | boolean | (value, cellProps) => boolean | Turns editing on. The column's editable decides per column. |
editStartEvent | 'click' | 'dblclick' | Event that opens a cell editor (default 'dblclick'). Enter opens it on the active row. |
onEditComplete | ({ value, columnId, rowId, data, rowIndex }) => void | Called when Enter, Tab or blur completes an edit. The grid does not change the row, you own the data. |
onEditStart / onEditCancel | ({ rowId, columnId }) => void | Called when an editor opens, or when Escape cancels it. |
Keyboard and events
| Prop | Type | Description |
|---|---|---|
activeIndex / defaultActiveIndex / onActiveIndexChange | number | Active row. Arrow keys, Home, End, PageUp and PageDown move it. With onRowReorder, Alt+ArrowUp/ArrowDown move the row itself. |
onKeyDown | (event) => void | Key events of the grid root. They run after the grid's own handling unless you call preventDefault. |
onRowClick / onRowDoubleClick | (rowIndex, row) => void | Row clicks. |
contextMenuItems | menu items or (row) => items | Row context menu, built on @azrico/controls. |
contextMenuClassName | string | Extra classes of the row context menu, which is portaled to <body>. |
hideContextMenu | boolean | Leaves the browser's context menu available. |
longPressContextMenu | boolean | A touch held on a row for 500ms opens the row context menu, like a right-click, and the click that ends the press is swallowed (default true). |
dontAutoCloseContextMenu / escapeClosesContextMenu | boolean | Context menu closing behaviour (both default false). |
Misc
| Prop | Type | Description |
|---|---|---|
gridRef | MutableRefObject<AzGridApi | null> | Receives the grid API. |
theme | 'light' | 'dark' | string | Theme class, see Themes. |
rtl | boolean | Lays the grid out right to left. Without it the grid stays left to right on RTL pages. |
className / style | Classes and style of the grid container. | |
ariaLabel | string | Accessible name of the grid. |
delayedSize | boolean | Waits for the container size before laying out, for animated containers. |
children | ReactNode | Rendered after the grid, inside its container. |
Column definition
| Field | Description |
|---|---|
name | Key of the value in the row. field, key and columnName are aliases. |
header | Header content. title is an alias. |
type | 'string', 'number', 'select' or 'bool'. Picks the editor and the filter. combo, boolean and truefalse are aliases. |
items | Options of a select column, plain values or { id, label } objects. |
editable | boolean or (value, cellProps) => boolean. Typed columns are editable by default. |
renderEditor / editorProps | Custom editor (editorProps) => node, and extra props for the editor. |
render | Custom cell content (cellProps) => node. cellProps has value, data, rowIndex, rowId, depth, selected and active. |
onRender | (cellProps, { data }) => void, runs before render and may change cellProps.className / cellProps.style. |
className | String or (cellProps) => string, added to every cell. |
cellDOMProps / headerDOMProps | Extra DOM attributes. Yours win over the grid's. |
ariaLabel | (value, data) => string, the aria-label of the cells. |
width / defaultWidth, minWidth, maxWidth | Widths in px. A saved width replaces width. |
flex / defaultFlex | Share of the leftover width. A resized or saved width replaces it. |
resizable, sortable, filterable, hideable | Per-column switches. |
sort | Comparator (a, b, rowA, rowB) => number, or false to disable sorting. |
filterEditor / filterEditorProps | 'text', 'number', 'select', 'bool' or a component, and its props (placeholder, dataSource). |
visible / defaultVisible | Controlled or initial visibility. |
pinned | 'left' or 'right'. Usually set through leftColumns / rightColumns. |
textAlign | 'start', 'center' or 'end'. |
Grid API
gridRef.current exposes:
| Member | Description |
|---|---|
focus() | Focuses the grid. |
setActiveIndex(index) / getActiveIndex() | Sets or reads the active row and scrolls it into view. |
getDOMNode() | The grid root element. |
visibleColumns | Visible columns in display order. |
getData() | Loaded top-level rows. Children are in __children. |
setOriginalData(rows) | Replaces the loaded rows without calling the dataSource. |
setItemAt(index, item, { replace? }) | Merges or replaces one loaded row. |
setItemPropertyAt(index, property, value) | Changes one value of a loaded row. |
refresh() / refreshColumn(name) | Re-renders every row, or the cells of one column. |
reload() | Drops the cache and calls the dataSource again. |
startEdit({ rowId, columnId }) / cancelEdit() | Opens or cancels an editor. |
setSelected(value) | Sets the row selection. |
hideColumnMenu() | Closes the header menu. |
DOM classes
The grid renders stable BEM class names. Style or query them freely. They are exported as AZGRID_CLASSES.
| Class | Element |
|---|---|
azgrid | Grid root (role="grid"), with azgrid--rtl when rtl is set, azgrid--clip-scrolled with clipScrolledCells and azgrid--reorderable while rows can be reordered |
azgrid__scroller | Scroll container of header, body and footer |
azgrid__header, azgrid__header-row, azgrid__header-cell | Header. Cells carry data-column |
azgrid__sort-tool, azgrid__menu-tool, azgrid__resize-handle | Header cell tools |
azgrid__filter-row, azgrid__filter-cell | Filter row |
azgrid__body, azgrid__row-wrapper, azgrid__row | Body and rows. Rows carry data-row-id |
azgrid__cell, azgrid__cell-content | Body cells. Cells carry data-column |
azgrid__row-index, azgrid__checkbox, azgrid__expander, azgrid__tree-tool | Built-in columns and tools |
azgrid__details | Row details block |
azgrid__editor | Wrapper of an open cell editor |
azgrid__footer, azgrid__footer-cell | Footer row |
azgrid__pagination | Pagination toolbar |
azgrid__load-mask, azgrid__empty | Load mask and empty text |
azgrid-row--menu-open | Row whose context menu is open |
azgrid-column-menu | Header menu popup (portaled to <body>, carries the theme class and data-column) |
azgrid-column-menu__submenu | "Columns" submenu popup, also has azgrid-column-menu |
azgrid-column-menu__list, azgrid-column-menu__separator | Item list (role="menu") and the line between groups |
azgrid-column-menu__item | Menu entry, with __icon, __label and (submenu trigger) __arrow children |
azgrid-column-menu__text, azgrid-column-menu__element | type: 'text' / type: 'element' entries of columnMenuItems |
Header menu items get a modifier naming the entry: --sort-asc, --sort-desc, --unsort, --columns (submenu
trigger), --column (a show/hide checkbox in the submenu, also carries data-column) and --custom (columnMenuItems).
State modifiers: --active (current sort, or isActive of a custom item), --checked (visible column) and --open
(submenu trigger while its submenu is shown).
Rows get --odd/--even, --selected, --active, --expanded and --editing modifiers, and --dragging,
--drop-before/--drop-after during a row reorder. Cells get --selected,
--active, --editing, --readonly, --pinned-left/--pinned-right and --pinned-edge modifiers. Header cells get
--sortable, --sorted-asc/--sorted-desc and --resizing modifiers.
Saved column widths
Set gridId to keep the column widths a user resizes across reloads. Widths are saved in localStorage and matched to columns by name.
import { AzGrid, setGridStorageScope, clearColumnSizes } from '@azrico/reactgrid';
setGridStorageScope(user.id); // once, before the grids render, so users of one browser don't share widths
<AzGrid gridId="current-stock" columns={columns} dataSource={rows} />;
clearColumnSizes('current-stock'); // reset, takes effect when the grid remounts
A column's width is used as its defaultWidth when gridId is set, so it can be resized.
Accessibility
The grid exposes ARIA grid semantics, so screen readers and automation tools can read it from the accessibility tree.
| Element | Attributes |
|---|---|
| Grid root | role="grid", aria-rowcount (total rows incl. header; the full data count when paginated), aria-colcount (visible columns), aria-label, aria-readonly="true" when the grid isn't editable, aria-busy="true" while loading |
| Header row | role="row", aria-rowindex="1" |
| Header cells | role="columnheader", aria-colindex, aria-sort (ascending / descending / none, sortable columns only), data-column="<column id>" |
| Rows | role="row", aria-rowindex (1-based, header is 1, includes the page offset), aria-selected when selection is enabled, data-row-id |
| Body cells | role="gridcell", aria-colindex, data-column="<column name>", aria-readonly="true" when the cell can't be edited |
| Row-number cells | role="rowheader" |
New props:
ariaLabel?: stringon the grid sets the accessible name of the grid.ariaLabel?: (value, data) => stringon a column setsaria-labelon its cells. Use it for icons, badges or values whose visible text isn't descriptive.
const columns = [{ name: 'status', header: 'Status', ariaLabel: (value) => `Status: ${value}` }];
<AzGrid ariaLabel="Orders" columns={columns} dataSource={rows} />;
Override any cell attribute with the column's cellDOMProps / headerDOMProps. Values you set there are kept.
An onRenderRow prop runs after the grid's own row attributes are set, so it can change them too.
Themes
Use theme="light" (default), theme="dark", or a custom name. The grid wrapper gets azgrid-scope and
azgrid-theme-<name>; the portaled column and context menus (.azgrid-column-menu, .azgrid-context-menu) get the
same theme class so they read the same variables. Custom names use light fallbacks for unspecified variables.
dark remains a deprecated alias. theme takes precedence.
Set --azgrid-* variables on a wrapper, :root, or a theme class. Each grid can use a different palette. Host
variables always win: the theme file only declares --azgrid-preset-* fallbacks, never public defaults.
<div className="my-grid">
<AzGrid theme="light" columns={columns} dataSource={rows} />
</div>
.my-grid {
--azgrid-accent: #345b93;
--azgrid-header-bg: #edf2f8;
--azgrid-row-bg-hover: #e4ecf7;
}
Row states are painted in this order (later wins): zebra odd/even, tree rows, active, hover (also while the row's context menu is open), selected. Pinned cells copy the row's current background so they stay opaque while scrolling.
Check the stylesheets with node config/check-theme.cjs; regenerate the preset blocks and the table below with
node config/generate-theme.cjs after editing the token manifest in that file.
CSS variables
All variables have light/dark fallbacks. Use valid CSS values for the named property.
--azgrid-locked-column-shadow accepts a complete box-shadow value. --azgrid-header-height is not a token: the
grid sets it on its root from the measured header so the filter row can stick under it.
| Variable | Controls |
|---|---|
--azgrid-bg | Grid background |
--azgrid-fg | Grid text |
--azgrid-border | Grid border and separators |
--azgrid-font-family | Grid and control fonts |
--azgrid-radius | Grid, input and menu corner radius; default 0 |
--azgrid-accent | Focus outlines, sort arrows, resize handle, checkboxes, cell selection and the row reorder drop line |
--azgrid-transparent | Transparent structural surfaces (body, row wrapper, tool buttons) |
--azgrid-muted-fg | Read-only cells, inactive sort and menu tools, deleted-row strike |
--azgrid-shadow-color | Menu shadow colour |
--azgrid-header-bg | Header and footer background |
--azgrid-header-fg | Header and footer text |
--azgrid-header-border | Header, filter row and footer separators |
--azgrid-header-font-size | Header font size |
--azgrid-header-text-transform | Header text transform |
--azgrid-header-letter-spacing | Header letter spacing |
--azgrid-header-font-weight | Header font weight |
--azgrid-row-bg-even | Even and non-zebra rows |
--azgrid-row-bg-odd | Odd rows (zebra grids) |
--azgrid-row-bg-hover | Hovered rows, rows with an open menu, hovered buttons |
--azgrid-row-fg-hover | Hovered row text |
--azgrid-row-bg-selected | Selected rows and cells |
--azgrid-row-fg-selected | Selected row and cell text |
--azgrid-row-bg-active | Active (focused) rows |
--azgrid-cell-border | Cell border colour |
--azgrid-cell-border-width | Cell border width |
--azgrid-cell-padding | Data and footer cell padding |
--azgrid-filter-bg | Filter row, input, editor and search background |
--azgrid-filter-fg | Filter input and editor text |
--azgrid-filter-border | Filter input borders |
--azgrid-filter-placeholder | Input placeholder text |
--azgrid-filter-focus-ring | Focused input border and ring, open editor outline |
--azgrid-pagination-bg | Pagination toolbar and control background |
--azgrid-pagination-fg | Pagination text and icons |
--azgrid-pagination-border | Pagination control borders |
--azgrid-menu-bg | Column and context menu background |
--azgrid-menu-fg | Menu text and icons |
--azgrid-menu-border | Menu borders |
--azgrid-menu-bg-hover | Hovered and focused menu entries |
--azgrid-scrollbar-track | Scrollbar tracks |
--azgrid-scrollbar-thumb | Scrollbar thumbs |
--azgrid-empty-bg | Empty state background |
--azgrid-empty-fg | Empty state text |
--azgrid-loading-bg | Loading overlay background |
--azgrid-loading-fg | Loading text |
--azgrid-tree-bg | Tree grid row background |
--azgrid-tree-fg | Tree grid text and expand tools |
--azgrid-details-bg | Row details background |
--azgrid-locked-column-shadow | Pinned column edge shadow (complete box-shadow value) |
example
See src/example.tsx for light, dark and a complete custom deco palette.
Custom renderers must style their own content. External menu portal containers need
azgrid-scope, the matching azgrid-theme-<name> class and the host variables.