@helix-x/datagrid-ui - v0.2.0
    Preparing search index...

    @helix-x/datagrid-ui - v0.2.0

    @helix-x/datagrid-ui

    A dependency-free React data grid: virtualized rows, server-side paging / sorting / filtering, inline row editing, multi-select with per-row locking, per-row background colours, CSV export of every matching row (fetched from the server, up to 1000 by default), clipboard copy and persisted column preferences.

    Its only runtime requirement is React. Styling is plain Tailwind utility classes, so there is no CSS file to import and no theme engine to configure.

    A runnable Vite gallery with a mock stock-market API lives in example/:

    cd example
    npm install
    npm run dev

    Six examples, all against a real server-side backend:

    Server-side market data 480 symbols; paging, sorting and all four filter kinds resolved on the server, plus inline editing with server-side validation
    Images in rows company logos and analyst avatars, lazy-loaded with reserved boxes and error fallbacks
    Live updates an SSE feed patched in with api.updateRows() — no refetch, no lost scroll position
    Column layout resize limits, drag-to-reorder around hidden columns, pinning on both edges, all persisted under one storageKey
    Paging without a total a backend that returns lastRow: -1; the pager reads "of many" and stops at the first short page
    Theme customization accent, density and dark mode driven entirely by CSS custom properties

    Each example sits behind a Preview / Code toggle — the running grid, or its real source — and is followed by a How it works section pairing the explanation with the code that implements it, deep-linked to the exact lines on GitHub. Every snippet is extracted from the source files at build time, so none of it can drift.

    The package is a standalone module — it can be installed from a registry, a tarball, a git URL, or a relative path:

    npm install @helix-x/datagrid-ui
    # or, consuming it from a checkout next to your app:
    npm install file:../helix-x-datagrid

    React 18 or 19 must already be present; it is a peer dependency and is never bundled into the output.

    The grid renders Tailwind utility classes. Tailwind only generates classes it can see, and it skips node_modules during automatic content detection, so point it at the shipped bundle explicitly:

    /* your Tailwind v4 entry point */
    @import "tailwindcss";
    @source "../node_modules/@helix-x/datagrid-ui/dist/index.js";

    On Tailwind v3, add the same path to content in tailwind.config.js, and define the four colour variables below as well. Row backgrounds (stripe, selection, editing, hover, and anything you return from getRowClassName, which takes the stripe's place) are opaque colours mixed at runtime from var(--color-…), so the sticky checkbox column never shows the cells scrolling underneath it. v4 emits those variables for you; v3 emits none, and without them rows render with no background at all:

    /* Tailwind v3 only */
    :root {
    --color-white: theme(colors.white);
    --color-gray-50: theme(colors.gray.50);
    --color-gray-900: theme(colors.gray.900);
    --color-brand-500: theme(colors.brand.500);
    }

    The grid uses the brand, gray and error colour families and the class-based dark variant. If your project does not already define them:

    @custom-variant dark (&:is(.dark *));

    @theme {
    --color-brand-25: #f2f7ff;
    --color-brand-50: #ecf3ff;
    --color-brand-200: #c2d6ff;
    --color-brand-400: #7592ff;
    --color-brand-500: #465fff;
    --color-brand-600: #3641f5;

    --color-error-50: #fef3f2;
    --color-error-300: #fda29b;
    --color-error-500: #f04438;
    --color-error-600: #d92d20;
    --color-error-700: #b42318;
    }
    import { DataGrid, type ColumnDef, type GridApi } from '@helix-x/datagrid-ui';

    const columns: ColumnDef<Person, Ctx>[] = [
    { field: 'id', header: 'ID', width: 90, filter: 'number' },
    { field: 'name', header: 'Name', flex: 1, filter: 'text', editable: true },
    { field: 'status', header: 'Status', filter: 'set',
    filterParams: { values: ['ACTIVE', 'CLOSED'] },
    editable: true, editor: 'select',
    editorParams: { options: [{ label: 'Active', value: 'ACTIVE' }] } },
    ];

    <DataGrid
    columns={columns}
    getRowId={(row) => row.id}
    dataSource={{ getRows: (request, signal) => api.list(request, signal) }}
    storageKey="people"
    context={{ onEdit }}
    onRowCommit={async (draft) => {
    const parsed = schema.safeParse(draft);
    if (!parsed.success) return { ok: false, errors: toFieldErrors(parsed.error) };
    await api.save(draft);
    return { ok: true };
    }}
    apiRef={apiRef}
    />

    Two optional props decide things per row, from its data:

    <DataGrid
    // ...
    isRowSelectable={(row) => row.status !== 'CLOSED'}
    getRowClassName={(row) => (row.status === 'CLOSED' ? 'bg-gray-100 dark:bg-gray-800' : '')}
    />
    • isRowSelectable — a row it returns false for shows a padlock in place of its checkbox and is skipped by shift-ranges, the header's select-all and api.selectAll(). The header checkbox is disabled when no loaded row can be selected. An id selected while its row was selectable stays selected if the row later stops being so; call api.clearSelection() when that matters.
    • getRowClassName — a background for the row. A non-empty result replaces the stripe; the editing, selected and hover colours still win, and pinned cells take the row's colour. The colour must be opaque: the sticky checkbox and pinned cells paint the row's background, and a translucent one lets scrolled cells show through. The classes live in your source, so your Tailwind build sees them without any extra @source.

    getRows receives a request that is structurally identical to ag-grid's server-side row model request, so an endpoint written for that model works unchanged:

    { startRow, endRow, sortModel: [{ colId, sort }], filterModel,
    rowGroupCols: [], valueCols: [], pivotCols: [], pivotMode: false, groupKeys: [] }

    (rowGroupCols, valueCols and groupKeys are filled only while the grid is grouped; see Row grouping.)

    and returns { rows, lastRow }. Filter models use the same four shapes (text, number, date, set) that ag-grid emits.

    One level of grouping, resolved by the server like sorting and filtering. Mark the columns that may be grouped by, and the columns to aggregate on group rows:

    const columns: ColumnDef<Stock>[] = [
    { field: 'symbol', header: 'Symbol' },
    { field: 'sector', header: 'Sector', enableRowGroup: true },
    { field: 'volume', header: 'Volume', aggFunc: 'sum' },
    ];

    <DataGrid columns={columns} defaultRowGroupColId="sector" {...rest} />

    The user groups from a column's ⋮ menu ("Group by this column") and ungroups from the "Grouped by" chip in the toolbar. You can also drive it yourself with rowGroupColId / onRowGroupColIdChange or api.setRowGroup(colId | null). Uncontrolled grouping is persisted under storageKey.

    While grouped, the server answers two kinds of request, the same ones ag-grid's server-side row model sends:

    Request Return
    rowGroupCols: [{ id, field }], groupKeys: [] One row per group: the group value under field, each valueCols aggregate under its column's field, and childCount. lastRow is the number of groups, and the pager pages over groups.
    rowGroupCols: [{ id, field }], groupKeys: ['<value>'] That group's rows, paged by startRow/endRow, with the usual sort and filters.

    Opening a group fetches its rows in blocks of groupChildBlockSize (default 100) and offers "Load more" when there are more. Group keys are strings: the group row's value through String(). Changing the sort, the filters or the grouping closes every group.

    Limits: one level only; group rows cannot be selected or edited; CSV export always fetches ungrouped rows, not group summaries; the grid never computes an aggregate itself.

    Column definitions should be memoised on []. Anything volatile — in-flight ids, permission checks, event handlers — belongs in context, which is passed to every cellRenderer. Changing context re-renders cells without rebuilding a single column definition.

    Full generated reference: https://suryakand.github.io/datagrid-ui/

    Every exported symbol carries a doc comment — parameters, return values, defaults, and runnable examples on the types you actually write (ColumnDef, DataGridProps, HxDataSource, GridApi). The comments ship inside dist/index.d.ts too, so they appear on hover in any editor without visiting the site.

    Build it locally:

    npm run docs         # -> docs/
    npm run docs:watch

    The generator is TypeDoc, not JSDoc. It reads the same /** ... */ comments but takes parameter and return types from TypeScript itself, so signatures cannot drift from the code the way hand-written @param {Type} annotations do.

    typedoc.json turns on link, export and coverage validation, and CI builds with --treatWarningsAsErrors. A broken {@link}, a type referenced from the public API but never exported, or a new export with no doc comment fails the docs build rather than shipping a gap.

    npm install       # build toolchain only; React comes from the host app
    npm run build # dist/ — ESM, CJS and .d.ts
    npm run dev # rebuild on change
    npm run typecheck
    npm run docs # docs/ — generated API reference

    When an app consumes this package through a relative path or npm link, npm creates a symlink and Node can resolve a second copy of React from the package's own tree. Nothing here installs React (.npmrc sets legacy-peer-deps so the peer range is not auto-installed), but bundlers should still be told to dedupe:

    // vite.config.ts
    resolve: { dedupe: ['react', 'react-dom'] }

    A push to main publishes the version in package.json to npmjs.org and/or a custom registry, tags it, creates the GitHub release with notes built from the commits, bumps main to the next version, and then deploys the API reference to GitHub Pages. Failed releases can be retried, and released versions can be copied to another registry, from the Actions tab.

    See RELEASING.md for the workflows, the required secrets and variables, and troubleshooting.

    npm pack produces an installable tarball; prepack rebuilds dist/ first, so the published artifact is never stale.

    Multi-level row grouping, pivoting, tree data, master/detail, variable row heights, and .xlsx output (CSV with a BOM opens natively in Excel).