ui-grid Documentation

TypeScript angular & javascript repository by @angular-ui. AI agent rules & system prompts with 5.4k stars.

5,384 stars TypeScript TypeScript 1 file ยท ~27,809 tokens #angular#javascript#ui-grid
RAW Doc

Technical Documentation: angular-ui/ui-grid

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)




[Discord Community](https://discord.gg/Baz4w8ZWN)

UI Grid โ€” 5.x

The modern web data grid for Angular, React, and Web Components. Every feature is free and open source.

Rust, WASM implementation, egui, and native adapter source and documentation live in orneryd/uiGrid.

v5.x + is in use by my other project, NornicDB, in the web ui components (It's a high-performance golang graph+vector database if you want to take a look)

A from-scratch rewrite of the original by the original author. Same gridOptions / columnDefs / onRegisterApi api surface, modern Angular signals internals, and zero legacy baggage.

Just like the original grid, it will NEVER be monetized. This is purely my contribution to the greater community.

It proves that the datagrid cabal wants you to think it's hard to write a data grid. Well, it's not, and nobody should be paying to group data.

Live Demo & Docs | npm | Rust Project | Used by NornicDB


Why Remastered?

  • Original authorship โ€” built by the same engineer who created AngularJS ui-grid, with a decade of hindsight on what worked and what didn't
  • Familiar API โ€” if you used the original ui-grid, you already know this one: gridOptions, columnDefs, onRegisterApi, gridApi.core.*
  • Modern internals โ€” shared vanilla web component core with Shadow DOM encapsulation; Angular and React wrappers are thin bridges that project framework templates into the vanilla element via slot-based portals
  • No legacy โ€” no $scope, no Bower, no Grunt, no jQuery, no module system from 2013

Feature Comparison

Everything below ships free and MIT-licensed. No enterprise tier, no license keys, no per-developer fees.

Feature UI Grid ag-Grid Community ag-Grid Enterprise Vaadin Grid Kendo UI Syncfusion
Sorting Free Free โ€” Free Paid Community*
Filtering Free Free โ€” Free Paid Community*
Row Grouping Free โ€” ~$999/dev/yr โ€” Paid Community*
Tree Data Free โ€” ~$999/dev/yr Free Paid Community*
Master/Detail Rows Free โ€” ~$999/dev/yr โ€” Paid Community*
Inline Cell Editing Free Free โ€” Pro ~$159/dev/mo Paid Community*
Row Selection Free Free โ€” Free Paid Community*
Column Resizing Free Free โ€” Free Paid Community*
CSV Export Free Free โ€” โ€” Paid Community*
Excel Export Free โ€” ~$999/dev/yr โ€” Paid Community*
PDF Export Free โ€” ~$999/dev/yr โ€” Paid Community*
Virtual Scrolling Free Free โ€” Free Paid Community*
Pagination Free Free โ€” Free Paid Community*
Column Pinning Free Free โ€” Free Paid Community*
Column Reordering Free Free โ€” Free Paid Community*
Save/Restore State Free Free โ€” โ€” Paid โ€”
Infinite Scroll Free Free โ€” Free Paid Community*
Keyboard Cell Nav Free Free โ€” โ€” Paid Community*
Row Edit (dirty/save) Free โ€” โ€” โ€” โ€” โ€”
Cell Validation Free โ€” โ€” โ€” โ€” โ€”
CSV/JSON Import Free โ€” โ€” โ€” โ€” โ€”
Shadow DOM Free โ€” โ€” โ€” โ€” โ€”
Web Component Build Free โ€” โ€” Native โ€” โ€”
Feature Tree-Shaking Free โ€” โ€” โ€” โ€” โ€”
SSR Support Free โ€” ~$999/dev/yr โ€” โ€” โ€”
i18n (6 locales built-in) Free Free โ€” Free Paid Community*
React Yes Wrapper Wrapper No Wrapper Wrapper
Angular Yes Wrapper Wrapper No Wrapper Wrapper
License MIT MIT Commercial Apache/Comm. Commercial Comm./Community
Price $0 $0 ~$999/dev/yr $159/dev/mo (Pro) ~$799/dev/yr See below

Bold = features where UI Grid gives you for free what competitors charge for or don't offer at all.

Syncfusion Community license is free for companies with <$1M annual revenue, โ‰ค5 developers, and โ‰ค$3M outside capital. Their paid tier requires a custom quote. Kendo UI ranges $799โ€“$1,299/dev/yr depending on support level. Prices are approximate and subject to change.


Quick Start

bash
npm install @ornery/ui-grid

The WASM runtime is installed transitively and loaded through your application
bundler. No angular.json asset entry or dist/ui-grid-wasm-web copy is needed,
and applications should not register the TypeScript pipeline as a WASM engine.

For an unbundled deployment that hosts the web-target WASM files separately,
configure their asset root before mounting a grid:

typescript
import { setUiGridWasmAssetBase } from '@ornery/ui-grid';

setUiGridWasmAssetBase('https://cdn.example.com/ui-grid-assets/');

The configured root must contain
dist/ui-grid-wasm-web/ui_grid_wasm.js and its adjacent
ui_grid_wasm_bg.wasm. If WASM initialization is unavailable, the grid remains
functional through its TypeScript fallback.

Angular Component

typescript
import { Component } from '@angular/core';
import { GridOptions, UiGridComponent } from '@ornery/ui-grid';

@Component({
  selector: 'app-my-grid',
  imports: [UiGridComponent],
  template: `<app-ui-grid [options]="gridOptions" />`,
})
export class MyGridComponent {
  gridOptions: GridOptions = {
    id: 'my-grid',
    data: [
      { name: 'Alice', role: 'Engineer', salary: 120000 },
      { name: 'Bob', role: 'Designer', salary: 95000 },
    ],
    columnDefs: [
      { name: 'name' },
      { name: 'role' },
      { name: 'salary', type: 'number', align: 'end' },
    ],
    onRegisterApi: (api) => {
      this.gridApi = api;
    },
  };
}

React

bash
npm install @ornery/ui-grid-react @ornery/ui-grid-core @ornery/ui-grid-vanilla
tsx
import { UiGrid } from '@ornery/ui-grid-react';
import type { GridOptions } from '@ornery/ui-grid-core';

function MyGrid() {
  const options: GridOptions = {
    id: 'my-grid',
    data: [
      { name: 'Alice', role: 'Engineer', salary: 120000 },
      { name: 'Bob', role: 'Designer', salary: 95000 },
    ],
    columnDefs: [
      { name: 'name' },
      { name: 'role' },
      { name: 'salary', type: 'number', align: 'end' },
    ],
  };

  return <UiGrid options={options} />;
}

Web Components (Vanilla)

The grid's rendering engine is a framework-free custom element () built on `@ornery/ui-grid-core` with pure DOM rendering and Shadow DOM encapsulation. Both the Angular and React wrappers are thin bridges around this same element โ€” they mount , pass options, and project framework-specific templates into it via a slot-based portal system.

bash
npm install @ornery/ui-grid-vanilla @ornery/ui-grid-core

No-bundler browser usage is supported by the browser bundle. It includes the core runtime and registers `` when loaded:

html
<script type="module" src="./ui-grid-element.js"></script>

<ui-grid-element
  grid-id="static-grid"
  enable-sorting
  enable-filtering
  column-defs='[{"name":"name"},{"name":"role"}]'
  data='[{"name":"Alice","role":"Engineer"}]'
>
</ui-grid-element>

When building from this repo, npm run build:vanilla writes that file to projects/ui-grid-vanilla/dist/browser/ui-grid-element.js. Use that browser bundle for static script-tag consumption; projects/ui-grid-vanilla/dist/index.js is CommonJS and projects/ui-grid-vanilla/dist/index.mjs is the package ESM entry for bundlers or import-map setups.

Declarative HTML usage:

html
<ui-grid-element
  grid-id="vanilla-demo"
  title="Team Roster"
  enable-sorting
  enable-filtering
  column-defs='[
    { "name": "name" },
    { "name": "role" },
    { "name": "salary", "type": "number", "align": "end" }
  ]'
  data='[
    { "name": "Alice", "role": "Engineer", "salary": 120000 },
    { "name": "Bob", "role": "Designer", "salary": 95000 }
  ]'
>
</ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement(); // registers <ui-grid-element>
</script>

Supported declarative inputs include boolean flags (enable-sorting, enable-filtering), scalar attributes (grid-id, title, row-height), and JSON attributes (column-defs, data).

For callbacks, function-valued column definitions, or high-frequency updates, use the options property:

typescript
import { mountVanillaUiGrid } from '@ornery/ui-grid-vanilla';

await mountVanillaUiGrid(document.getElementById('app'), {
  id: 'mounted-grid',
  data: [{ name: 'Alice', role: 'Engineer' }],
  columnDefs: [{ name: 'name' }, { name: 'role' }],
});

Features

  • Sorting โ€” click column headers to cycle asc/desc/none, custom comparators, programmatic API
  • Filtering โ€” per-column inputs with conditions: contains, exact, startsWith, endsWith, greaterThan, regex, custom predicates
  • Row Grouping โ€” nested multi-column grouping with collapsible group headers
  • Tree View โ€” hierarchical data with expand/collapse per node, arbitrary nesting depth
  • Expandable Rows โ€” master/detail pattern with custom templates (Angular ng-template, React render prop, or vanilla <template> slot)
  • Cell Editing โ€” inline spreadsheet-style editing with full keyboard navigation (Tab, Enter, Escape), edit-on-focus, Enter/Tab commit+move across editable columns
  • Keyboard Cell Navigation โ€” Arrow/Tab/Home/End navigation with wrap/clamp modes, focus persistence across re-renders, keyDownOverrides for custom key handling, full gridApi.cellNav surface
  • Row Selection โ€” click/shift/ctrl/drag-paint mouse selection, keyboard (Space, Ctrl+A), row-header checkbox column, select-all header, isRowSelectable hook, 13 options, 18 API methods, 3 events
  • Column Resizing โ€” drag column borders to resize, programmatic API, persisted via save/restore state
  • Row Edit โ€” dirty/saving/error row lifecycle with rowEditWaitInterval debounce, setSavePromise pattern, auto-retry on error, visual row state indicators
  • Cell Validation โ€” declarative per-column validators (built-in required, minLength, maxLength + custom), async validator support, invalid cell markers with error messages, gridApi.validate surface
  • Pagination โ€” client-side or external pagination with configurable page sizes
  • Infinite Scroll โ€” bi-directional infinite scrolling with loading state management, needLoadMoreData/needLoadMoreDataTop events, full public API
  • Column Pinning โ€” freeze columns left or right with CSS position: sticky, programmatic API, save/restore state
  • Column Moving โ€” HTML5 native drag-and-drop column reordering
  • CSV Export โ€” download visible/selected/all rows with formula-injection protection, full option matrix (separator, header filter, field callbacks, BOM compatibility)
  • Excel Export โ€” ExcelBuilder-compatible sheet data with native numeric/boolean types preserved, configurable filename/sheet/header/custom formatters
  • PDF Export โ€” pdfMake-ready document definition with orientation/page size/styles/header/footer/custom formatter, auto-download when pdfMake is available
  • CSV/JSON Import โ€” file picker or programmatic import, full CSV parser (quoted values, escaped quotes, CRLF), header-to-column mapping, integrates with row-edit for dirty marking
  • Export/Import Menu โ€” buildGridExporterMenuItems() with per-format and per-scope flags, i18n-driven menu labels
  • Virtual Scrolling โ€” virtual scroll viewport, auto-enabled at 40+ rows
  • Save/Restore State โ€” serialize and restore sort, filters, grouping, collapsed groups, pinning, column order, column widths, pagination, selection, focused cell, tree/expandable expansion, and scroll position (per-field opt-in flags)
  • Auto Resize โ€” ResizeObserver-driven viewport height recalculation
  • Custom Cell Templates โ€” Angular ng-template, React cellRenderers map (per-column render functions), vanilla <template> slots with slot-based portal projection
  • Shadow DOM โ€” encapsulated styles with CSS custom property and ::part() hooks
  • Web Component โ€” ships as ``, a framework-free vanilla custom element (@ornery/ui-grid-vanilla); Angular and React wrappers mount this same element and project templates into it
  • Feature-Flag Builds โ€” compile-time tree-shaking of unused features
  • i18n โ€” 6 locales built-in (English, Spanish, French, German, Japanese, Simplified Chinese), runtime gridApi.i18n for language switching, fallback chain (options.labels โ†’ current locale โ†’ en-US), register additional locales via gridApi.i18n.add()
  • SSR Support โ€” server-side rendering with platform-safe guards

Theming

The grid renders inside Shadow DOM. Customize it via the public --ui-grid-* CSS custom properties:

css
.my-app {
  --ui-grid-surface: #1e1b2e;
  --ui-grid-accent: #8b5cf6;
  --ui-grid-header-background: #2d2640;
  --ui-grid-cell-color: #e2e0f0;
  --ui-grid-border-color: rgba(139, 92, 246, 0.2);
  --ui-grid-row-hover: #322e4a;
}

Legacy --app-ui-grid-* aliases remain supported as a fallback for older app themes, but new consumer theming should target only --ui-grid-*.

Target structural elements with ::part():

css
app-ui-grid::part(header) {
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

See docs/theming.md for the full CSS variable reference grouped by grid section, ::part() hooks, and the demo app's 4-mode theme system.


Custom Builds

Ship only the features you use:

bash
# Only sorting and filtering โ€” everything else is tree-shaken
node scripts/build-grid.mjs --features sorting,filtering

# Bake in a locale at build time
node scripts/build-grid.mjs --features sorting,filtering,pagination --locale i18n/fr-FR.json

# See all available flags
node scripts/build-grid.mjs --list

See docs/custom-builds.md for the full feature flag table and build presets.


Documentation

Guide Description
Getting Started Install, minimal setup, run the demo
Features Overview of all features with code examples
Theming CSS custom properties, ::part() hooks, sample themes
API Reference GridOptions, GridColumnDef, UiGridApi
Cell Editing Keyboard navigation, conditional editing, API
Tree View Hierarchical data, options, API
Expandable Rows Master/detail, template context, API
Custom Builds Feature flags, build presets, locale baking
Web Component Vanilla `` custom element usage
Internationalization Runtime overrides, build-time locales
Accessibility ARIA roles, keyboard navigation, screen reader support
Rust Project Rust/WASM implementation, egui, and native adapters

Interactive versions of all web documentation are also available in the live demo.


Development

bash
npm start          # Dev server at localhost:4200
npm test           # Run tests (Vitest)
npm run build      # Production build
npm run build:library   # Build the library (ng-packagr)
npm run start:vanilla   # Run the framework-neutral browser demo at 127.0.0.1:4174

Compatibility

Dependency Version
Angular 21.2
TypeScript 5.9
RxJS 7.8
Node 22.20
npm 11.11

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Run npm test to ensure all tests pass
  4. Submit a pull request

License

MIT


Overview (`projects/ui-grid-solid/README.md`)

@ornery/ui-grid-solid

SolidJS adapter for the existing uiGrid web component. Grid behavior, state,
rendering and event subscriptions stay in the vanilla renderer and core.

Install

sh
npm install @ornery/ui-grid-solid @ornery/ui-grid-core @ornery/ui-grid-vanilla [email protected] @solidjs/[email protected]

This package is developed against [email protected]. SolidJS 1 is not supported.
Use a SolidJS JSX compiler/plugin with jsxImportSource: "@solidjs/web".

tsx
import { createSignal } from 'solid-js';
import { UiGrid, type UiGridApi } from '@ornery/ui-grid-solid';

export function App() {
  const [rows, setRows] = createSignal([{ id: 1, name: 'Alice' }]);
  let api: UiGridApi | undefined;
  return <div style={{ height: '400px' }}>
    <button onClick={() => setRows([{ id: 1, name: 'Bob' }])}>Update</button>
    <UiGrid
      options={{
        id: 'users', data: rows(), columnDefs: [{ name: 'name' }],
        rowIdentity: (row) => String(row['id']), enableSorting: true,
      }}
      onRegisterApi={(value) => { api = value; }}
      onError={(error) => console.error(error)}
    />
  </div>;
}

options uses the existing GridOptions type. Replace options/data through
signals; mutating a plain array in place does not notify Solid. Top-level
reactive option getters are tracked. Updates assign options to the same element,
preserving the controller's interactive state. Give the parent a definite height.

onRegisterApi receives a compatible UiGridApi whose mutating operations also
refresh the Solid renderer bridges; options.onRegisterApi also runs. As in
vanilla, registration callbacks may run again on options updates.
Use api.core.on and other existing API event groups for subscriptions, and
dispose application-owned subscriptions with Solid's onCleanup.

Lifecycle and SSR

The server export renders only a host div. The vanilla module is dynamically
imported inside a client effect. Disposing the component cancels pending mount
work and removes the element, invoking vanilla's disconnectedCallback cleanup.
Initialization failures call onError when supplied, otherwise surface globally.

Correction to the contribution plan: the published [email protected]
package does not export onMount. This adapter uses SolidJS's two-argument
createEffect with a constant client guard for mounting, and a separate tracked
effect for options. It does not use onSettled as a mount hook.

Source exports use the solid condition so SolidStart can compile for its target.
Precompiled browser and server ESM exports are also included.

The examples/solid-start route example covers server
rendering, a reactive data update after hydration, and route navigation that
unmounts and remounts the grid.

Local development

sh
npm ci --prefix projects/ui-grid-solid
npm run build:solid
npm run test:solid
npm run start:solid

build:solid builds core, vanilla, then Solid using a cross-platform Node script.
In PowerShell environments that block npm.ps1, use npm.cmd for these commands.

Solid JSX cells

Pass cellRenderers, keyed by column name:

tsx
const cellRenderers: UiGridCellRenderers = {
  name: (context) => <strong>{String(context.value)}</strong>,
};
// Import UiGridCellRenderers from @ornery/ui-grid-solid.
<UiGrid options={options()} cellRenderers={cellRenderers} />;

Each renderer runs under the grid's Solid context and receives reactive getters
for value, $implicit, row, column, and rowIndex. Read these getters
inside JSX or tracked computations; do not destructure them into plain values.
Cell-local signals survive value updates while the slot remains mounted.

The bridge follows vanilla slot events and API row updates. Removed/virtualized
cells dispose their Solid roots. Replacing a renderer function remounts its
cells; removing it restores vanilla rendering. Keep renderer functions stable
when local state should survive. Grid teardown removes all renderer roots and
the bridge's API subscription.

Renderer failures are reported through onError. The failed cell stays isolated
from the grid and remounts on the next options update, allowing it to recover.
The API returned by onRegisterApi wraps core.benchmark() so its result and
benchmarkComplete event include completion of the Solid renderer bridge.

The demo loads 100,000 rows across three columns by default. Click Move Name column to exercise
api.core.moveColumn().

Solid JSX headers

Pass headerRenderers, also keyed by column name:

tsx
const headerRenderers: UiGridHeaderRenderers = {
  name: (context) => <strong>{context.value} (Solid)</strong>,
};
// Import UiGridHeaderRenderers from @ornery/ui-grid-solid.
<UiGrid options={options()} headerRenderers={headerRenderers} />;

Header context exposes reactive value, $implicit, and column getters.
Local signals survive title and column option updates while the header remains
mounted. Hiding or removing a column disposes its root; showing it mounts a new
root. Renderer failures call onError and retry on the next options update.

Clicking a projected header cycles sorting through ascending, descending, and
none. An interactive child can call event.preventDefault() to suppress that
sort for its click. Programmatic column movement through
api.core.moveColumn() keeps projected headers aligned with the grid. A custom
header replaces the vanilla header contents, so it should render any additional
controls the application needs.

Solid JSX expandable details

Pass one detailRenderer and enable expandable rows:

tsx
const detailRenderer: UiGridDetailRenderer = (context) => (
  <section>
    <strong>{String(context.row['name'])}</strong>
    <span>Row {context.rowIndex}</span>
  </section>
);

<UiGrid
  options={{
    ...options(),
    enableExpandable: true,
    expandableRowHeight: 120,
  }}
  detailRenderer={detailRenderer}
/>;

Import UiGridDetailRenderer from @ornery/ui-grid-solid. The renderer receives
reactive row, $implicit, rowIndex, and expanded getters. Its Solid root
stays mounted while the same expanded detail remains visible, so local state
survives data updates, sorting, and index changes.

Use the existing api.expandable.toggleRowExpansion(), expandAllRows(), and
collapseAllRows() operations to control details. Collapsing, filtering out,
virtualizing out, or removing a row disposes its detail root. Errors are isolated
through onError and retry on the next options update.

Solid JSX cell editors

Pass cellEditors, keyed by column name, and enable editing for the grid and
column:

tsx
const cellEditors: UiGridCellEditors = {
  role: (context) => <select
    value={context.value}
    onChange={(event) => context.setValue(event.currentTarget.value)}
  >
    <option value="Engineer">Engineer</option>
    <option value="Designer">Designer</option>
  </select>,
};

<UiGrid
  options={{
    ...options(),
    enableCellEdit: true,
    columnDefs: [{ name: 'role', enableCellEdit: true }],
  }}
  cellEditors={cellEditors}
/>;

Import UiGridCellEditors from @ornery/ui-grid-solid. The reactive context
contains value, $implicit, row, column, rowIndex, setValue, commit,
and cancel. The first input, select, textarea, or focusable element receives
focus when editing starts.

Enter commits, Escape cancels, and moving focus outside the editor commits.
The Solid editor bridge follows the public editing events and uses vanilla's
internal editor input to synchronize values. The existing Grid API remains
responsible for parsing typed values, updating nested fields, raising edit
events, validation, and restoring cell rendering.
Buttons can invoke context.commit() or context.cancel() directly. The Solid
root is disposed whenever the edit session ends or the grid unmounts. Renderer
failures call onError and retry on the next options update.


Overview (`projects/ui-grid-vanilla/README.md`)

@ornery/ui-grid-vanilla

A vanilla Web Components wrapper for @ornery/ui-grid-core. Renders a high-performance data grid as a custom element with no framework dependency.

Architecture

The grid is built as a composition of autonomous custom elements, each following the @ornery/web-components pattern:

text
<ui-grid-element>                  โ† main grid element (shadow DOM)
  โ”œโ”€โ”€ ui-grid-shell.html           โ† declarative grid layout template
  โ”œโ”€โ”€ ui-grid-empty.html           โ† empty/no-options state template
  โ”‚
  โ”œโ”€โ”€ <ui-grid-header-cell>        โ† column header (sort, group, pin, resize)
  โ”œโ”€โ”€ <ui-grid-filter-cell>        โ† column filter input (shadow DOM + .html template)
  โ”œโ”€โ”€ <ui-grid-body-cell>          โ† data cell (class-managed, perf-optimized)
  โ”œโ”€โ”€ <ui-grid-group-row>          โ† grouping disclosure row (shadow DOM + .html template)
  โ”œโ”€โ”€ <ui-grid-pagination>         โ† pagination controls (shadow DOM + .html template)
  โ””โ”€โ”€ <ui-grid-template>           โ† declarative reusable template factory

Component Pattern

Components follow the standard @ornery/web-components lifecycle:

js
import template from './my-component.html';

class MyComponent extends HTMLElement {
  // 1. Attach shadow DOM in constructor
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  // 2. Declare instance properties as template context
  myProp = 'default';

  // 3. Render in connectedCallback
  connectedCallback() {
    this.myProp = this.getAttribute('data-my-prop') ?? 'default';
    template(this).connect(); // auto-renders into shadowRoot
  }
}

The .html template uses ES6 template literal syntax:

html
<div class="my-class">${this.myProp}</div>

Templates are compiled at build time by @ornery/web-components/vite (or /webpack, /esbuild, /rollup).

Performance Strategy

  • Hot-path components (ui-grid-body-cell, ui-grid-header-cell) skip template().connect() entirely โ€” the parent pre-renders innerHTML as a string, and the component only manages CSS classes via attributeChangedCallback. This avoids DOM construction overhead per cell.
  • Low-frequency components (ui-grid-filter-cell, ui-grid-group-row, ui-grid-pagination) use full template(this).connect() with shadow DOM for encapsulation and declarative rendering.

Template Bindings

Cell and row slot templates support dual binding syntax:

Syntax Example Description
{{ }} {{value}} Mustache-style, original grid syntax
${ } ${this.value} ES6 template literal, @ornery/web-components native

Both resolve against the same context: { $implicit, value, valueText, row, column, rowIndex }.

Installation

bash
npm install @ornery/ui-grid-vanilla @ornery/ui-grid-core

Usage

No-Bundler Browser Usage

Use the browser bundle when a static HTML host needs to consume the grid without Vite, Webpack, tsup, or another package-aware build step. The bundle includes @ornery/ui-grid-core and registers `` when the module loads.

After npm run build:vanilla from the repo root, the browser-ready file is written to projects/ui-grid-vanilla/dist/browser/ui-grid-element.js.

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>UI Grid Browser Bundle</title>
    <script type="module" src="./ui-grid-element.js"></script>
  </head>
  <body>
    <ui-grid-element
      grid-id="static-grid"
      enable-sorting
      enable-filtering
      column-defs='[{"name":"name"},{"name":"role"},{"name":"salary","type":"number","align":"end"}]'
      data='[{"name":"Alice","role":"Engineer","salary":120000},{"name":"Bob","role":"Designer","salary":95000}]'
    >
    </ui-grid-element>
  </body>
</html>

For imperative setup in the same no-bundler environment, import from that same file:

html
<div id="grid"></div>

<script type="module">
  import { mountVanillaUiGrid } from './ui-grid-element.js';

  await mountVanillaUiGrid(document.querySelector('#grid'), {
    id: 'static-mounted-grid',
    columnDefs: [{ name: 'name' }, { name: 'role' }],
    data: [{ name: 'Alice', role: 'Engineer' }],
    enableSorting: true,
    enableFiltering: true,
  });
</script>

The package dist/index.js file is CommonJS for Node/package consumers, and dist/index.mjs is package ESM for bundlers or import-map setups. Use dist/browser/ui-grid-element.js for direct browser loading.

Imperative (JavaScript)

js
import { mountVanillaUiGrid } from '@ornery/ui-grid-vanilla';

const grid = await mountVanillaUiGrid(document.getElementById('grid'), {
  id: 'my-grid',
  columnDefs: [
    { name: 'name', field: 'name', displayName: 'Name' },
    { name: 'age', field: 'age', displayName: 'Age', align: 'end' },
  ],
  data: [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 },
  ],
  enableSorting: true,
  enableFiltering: true,
});

Declarative (HTML attributes)

html
<ui-grid-element
  grid-id="my-grid"
  enable-sorting
  enable-filtering
  column-defs='[{"name":"name","field":"name"},{"name":"age","field":"age","align":"end"}]'
  data='[{"name":"Alice","age":30},{"name":"Bob","age":25}]'
>
</ui-grid-element>

Custom Cell Templates (slots)

html
<ui-grid-element id="grid">
  
  <template slot="cell-name">
    <strong>{{value}}</strong>
  </template>

  
  <template slot="cell-age">
    <span style="color: ${this.value > 30 ? 'red' : 'green'}">${this.value}</span>
  </template>
</ui-grid-element>

Declarative Reusable Templates

Define reusable components from HTML alone with <template is="ui-grid-template">:

html
<template is="ui-grid-template" name="ui-status-badge" status="unknown">
  <style>
    :host {
      display: inline-block;
    }
    .badge {
      padding: 2px 8px;
      border-radius: 4px;
    }
    .badge-active {
      background: #d1fae5;
      color: #065f46;
    }
    .badge-inactive {
      background: #fee2e2;
      color: #991b1b;
    }
  </style>
  <span class="badge badge-${this.status}">${this.status}</span>
</template>


<ui-grid-element>
  <template slot="cell-status">
    <ui-status-badge status="{{value}}"></ui-status-badge>
  </template>
</ui-grid-element>

Bundler Setup

Vite

js
import { defineConfig } from 'vite';
import webComponents from '@ornery/web-components/vite';

export default defineConfig({
  plugins: [webComponents()],
});

Webpack 5

js
import webComponents from '@ornery/web-components/webpack';

export default {
  plugins: [webComponents()],
};

tsup (esbuild)

ts
import { defineConfig } from 'tsup';
import webComponents from '@ornery/web-components/esbuild';

export default defineConfig({
  esbuildPlugins: [webComponents()],
});

API

`mountVanillaUiGrid(target, options, wasmModule?, tagName?)`

Mount a grid into a target element. Returns the grid element.

`defineStandaloneUiGridElement(tagName?)`

Register the `` custom element (and all sub-components).

`UIGridTemplate`

Exported class for the <template is="ui-grid-template"> element. Auto-registered when the grid is defined.

Grid Element Properties

See VanillaUiGridElement type โ€” all GridOptions fields are available as both HTML attributes and JS properties.


Overview (`openspec/changes/fix-wasm-runtime-loading/README.md`)

fix-wasm-runtime-loading

Bundle and initialize the UI Grid WASM runtime through published package dependencies instead of application-relative demo assets.


Overview (`projects/ui-grid-solid/examples/solid-start/README.md`)

SolidStart v2 example

Create a SolidStart v2 application, install the four packages shown in the
adapter README, then copy src/routes/index.tsx and src/routes/about.tsx into
the application's routes directory. SolidStart renders the grid host on the
server; the adapter imports and mounts the vanilla custom element after
hydration in the browser.

The links exercise route navigation. Leaving / unmounts the grid, including
its renderer roots and subscriptions. Returning to / creates a fresh grid.

SolidStart v2 uses vite.config.ts with solidStart() and a deployment plugin.
Keep the generated configuration from the SolidStart scaffold; this example
only replaces the route files.


Getting Started (`docs/getting-started.md`)

Getting Started

Get up and running with @ornery/ui-grid in under five minutes.

Install

bash
npm install @ornery/ui-grid

Peer dependencies: @angular/core, @angular/common, rxjs, tslib.

Minimal Angular Setup

The grid ships as a standalone component โ€” no module imports required:

typescript
import { Component } from '@angular/core';
import { GridOptions, UiGridComponent } from '@ornery/ui-grid';

@Component({
  selector: 'app-my-grid',
  imports: [UiGridComponent],
  template: `<app-ui-grid [options]="gridOptions" />`,
})
export class MyGridComponent {
  gridOptions: GridOptions = {
    id: 'my-grid',
    data: [
      { name: 'Alice', role: 'Engineer', salary: 120000 },
      { name: 'Bob', role: 'Designer', salary: 95000 },
    ],
    columnDefs: [
      { name: 'name' },
      { name: 'role' },
      { name: 'salary', type: 'number', align: 'end' },
    ],
  };
}

Required GridOptions Fields

Field Type Description
id string Unique grid identifier (used for CSV filenames and row IDs)
data readonly GridRecord[] Array of row objects
columnDefs readonly GridColumnDef[] Column definitions โ€” each needs at minimum a name

React

bash
npm install @ornery/ui-grid-react @ornery/ui-grid-core
tsx
import { UiGrid } from '@ornery/ui-grid-react';
import type { GridOptions } from '@ornery/ui-grid-core';

const options: GridOptions = {
  id: 'react-grid',
  data: [{ name: 'Alice', role: 'Engineer' }],
  columnDefs: [{ name: 'name' }, { name: 'role' }],
};

function App() {
  return <UiGrid options={options} />;
}

Web Components

Vanilla (`@ornery/ui-grid-vanilla`)

Framework-free, pure DOM with Shadow DOM. zero dependencies:

bash
npm install @ornery/ui-grid-vanilla @ornery/ui-grid-core

Declarative setup now works directly in HTML:

html
<ui-grid-element
  grid-id="vanilla-demo"
  enable-sorting
  enable-filtering
  column-defs='[{"name":"name"},{"name":"role"}]'
  data='[{"name":"Alice","role":"Engineer"}]'
>
</ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement();
</script>

Or bind the same fields as individual JS properties:

For static HTML hosts with no package-aware build step, use the browser bundle emitted by npm run build:vanilla:

html
<script type="module" src="./ui-grid-element.js"></script>

<ui-grid-element
  grid-id="static-grid"
  enable-sorting
  enable-filtering
  column-defs='[{"name":"name"},{"name":"role"}]'
  data='[{"name":"Alice","role":"Engineer"}]'
>
</ui-grid-element>

The repo build writes this file to projects/ui-grid-vanilla/dist/browser/ui-grid-element.js. It includes @ornery/ui-grid-core and auto-registers the element.

html
<ui-grid-element id="my-grid"></ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement();

  const grid = document.querySelector('#my-grid');
  grid.gridId = 'vanilla-props';
  grid.enableSorting = true;
  grid.enableFiltering = true;
  grid.columnDefs = [{ name: 'name' }, { name: 'role' }];
  grid.data = [{ name: 'Alice', role: 'Engineer' }];
</script>

The original bulk options property remains available when you need callbacks or function-valued configuration:

html
<ui-grid-element id="my-grid"></ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement();

  document.querySelector('#my-grid').options = {
    id: 'vanilla-demo',
    data: [{ name: 'Alice', role: 'Engineer' }],
    columnDefs: [{ name: 'name' }, { name: 'role' }],
  };
</script>

Run the Demo Locally

bash
git clone https://github.com/orneryd/ui-grid.git
cd ui-grid
npm ci
npm start

Open http://localhost:4200 to see the full demo with 100,000 rows, theming, and all features active. The live demo includes dedicated pages for Angular, React, and Web Components.

Next Steps

  • Features โ€” see everything the grid can do
  • Theming โ€” customize colors and layout via CSS custom properties
  • API Reference โ€” full GridOptions, GridColumnDef, and UiGridApi documentation
  • Web Component โ€” framework-neutral custom element usage
  • Rust Project โ€” Rust/WASM implementation, egui, and native adapters

Accessibility (`docs/accessibility.md`)

Accessibility

UI Grid is built with accessibility as a core concern. The grid applies WAI-ARIA grid semantics, supports full keyboard navigation, manages focus across editing and scrolling, and routes all visible text through the i18n system so screen reader announcements localize automatically.

ARIA Roles

The grid maps its structure to the WAI-ARIA grid pattern:

Element Role Notes
Grid container grid Top-level landmark; aria-label set from options.title (defaults to "Data grid")
Header row row Contains column headers
Header cell columnheader One per visible column
Body container rowgroup Groups all data rows
Data row row One per visible record
Data cell gridcell Contains cell content or editor
Group row row Collapsible group header with aria-expanded
Pagination navigation aria-label from labels.paginationPage

When tree view or expandable rows are active, aria-expanded is set on the toggle buttons to communicate open/closed state to assistive technology.

aria-* Attributes

Attribute Where Value
aria-label Grid container options.title or "Data grid"
aria-label Sort buttons Dynamic: "Sort", "Sort ascending", "Sort descending" (from i18n labels)
aria-label Group toggle buttons "Group by this column" / "Remove grouping"
aria-label Tree toggle buttons "Collapse" / "Expand"
aria-label Expand detail buttons "Expand row" / "Collapse row"
aria-label Filter inputs "Filter {column name}"
aria-label Cell editor input Column display name
aria-label Pagination nav "Page"
aria-label Previous/Next buttons "Previous" / "Next"
aria-sort Column headers "ascending", "descending", or "none"
aria-expanded Tree/expand/group toggles true or false
aria-hidden Decorative SVG icons true (with focusable="false")

All aria-label values are sourced from the i18n GridLabels object, so they localize automatically when you provide translated labels.

Keyboard Navigation

Every data cell receives tabindex="0", making the grid fully keyboard-navigable. The following keys are handled:

Grid Navigation

Key Action
Arrow Left Move focus one cell left (wraps to previous row)
Arrow Right Move focus one cell right (wraps to next row)
Arrow Up Move focus one row up
Arrow Down Move focus one row down
Tab Move to next cell (same as Arrow Right)
Shift + Tab Move to previous cell (same as Arrow Left)
Enter Move down one row (Shift+Enter = up)

Cell Editing

Key Action
F2 Begin editing the focused cell (if editable)
Backspace / Delete Clear cell value and enter edit mode
Any printable character Begin editing with that character as initial input
Escape Cancel edit, restore original value, return focus to cell
Enter Commit edit, move focus down (Shift = up)
Tab Commit edit, move focus to next cell (Shift = previous)

A "printable character" is any single-character key pressed without Ctrl, Meta, or Alt modifiers.

Focus Management

Focus management operates inside the grid's Shadow DOM:

  • Cell focus โ€” after keyboard navigation or virtual scroll repositioning, focusGridRenderedCell() queries the shadow root for the target cell by data-row-id and data-col-name attributes. A requestAnimationFrame retry handles cases where Angular hasn't rendered the target cell yet.
  • Editor focus โ€” when a cell enters edit mode, focusGridEditor() finds the editor input in the shadow root and calls input.select() to select all text.
  • Cancellation โ€” a focus token prevents stale focus calls from previous navigation actions.

Screen Reader Support

Visually hidden text (.sr-only / .ui-grid-sr-only) provides labels where icon-only buttons or implicit context would otherwise leave screen reader users without information:

  • Sort buttons include hidden text matching the aria-label
  • Group toggle buttons include hidden text for the action
  • Filter cells include a hidden "Filter {column name}" label
  • Group disclosure rows include hidden "Collapse group" / "Expand group" text
  • Pagination Previous/Next icon buttons include hidden text

The .sr-only class uses the standard clip technique (1px box, overflow: hidden, clip: rect(0,0,0,0)), available as both .sr-only and .ui-grid-sr-only in the grid's Shadow DOM styles.

Localized Announcements

Every screen reader label flows through the GridLabels i18n system. When you provide translated labels (via GridOptions.labels at runtime or a baked-in locale at build time), all ARIA labels and screen reader text update automatically. See the Internationalization guide for details.

Theming and Contrast

The grid's CSS custom property system supports high-contrast themes:

  • Override --ui-grid-cell-color, --ui-grid-muted-color, --ui-grid-surface, and --ui-grid-accent to meet WCAG contrast ratios
  • The data-color-mode / data-visual-mode attribute pattern (used in the demo app) demonstrates how to provide accessible light and dark modes without JavaScript theme-switching logic
  • Focus indicators use the --ui-grid-accent color for sort indicators and active states

Custom Cell Templates

When providing custom cell templates via cellTemplate or cellRenderer, ensure your content is accessible:

  • Use semantic HTML elements where possible
  • Add aria-label or aria-labelledby to interactive elements
  • Ensure custom controls are keyboard-reachable (tabindex="0" on interactive elements)
  • Maintain sufficient color contrast in custom-rendered content

Api Reference (`docs/api-reference.md`)

API Reference

Complete reference for GridOptions, GridColumnDef, and the UiGridApi runtime surface.

All types are exported from @ornery/ui-grid-core. The Angular package (@ornery/ui-grid) re-exports them for convenience.

Vanilla Custom Element Surface

The vanilla web component (@ornery/ui-grid-vanilla) supports three equivalent configuration styles for the subset of GridOptions that can be expressed without functions:

Declarative attributes

html
<ui-grid-element
  grid-id="accounts"
  title="Accounts"
  enable-sorting
  enable-filtering
  row-height="44"
  column-defs='[{"name":"name"},{"name":"status"}]'
  data='[{"name":"Acme","status":"Active"}]'
>
</ui-grid-element>

Individual JS properties

javascript
const grid = document.querySelector('ui-grid-element');
grid.gridId = 'accounts';
grid.title = 'Accounts';
grid.enableSorting = true;
grid.enableFiltering = true;
grid.rowHeight = 44;
grid.columnDefs = [{ name: 'name' }, { name: 'status' }];
grid.data = [{ name: 'Acme', status: 'Active' }];

Full `options` object

javascript
grid.options = {
  id: 'accounts',
  data: [{ name: 'Acme', status: 'Active' }],
  columnDefs: [{ name: 'name' }, { name: 'status' }],
  enableSorting: true,
  enableFiltering: true,
  onRegisterApi: (api) => {
    window.gridApi = api;
  },
};

Use attributes or individual properties for static configuration. Use options for callbacks like onRegisterApi, function-valued column definitions such as valueGetter, sortingAlgorithm, and cellRenderer, or when you prefer a single bulk assignment.

The vanilla merge order is:

text
{ ...attributeOptions, ...jsPropertyOptions }

That means explicit JS property values win over declarative attributes when both are present.

GridOptions

Field Type Default Description
id string required Unique grid identifier
data readonly GridRecord[] required Row data array
columnDefs readonly GridColumnDef[] required Column definitions
title string โ€” Grid heading text
rowHeight number 44 Row height in pixels
headerRowHeight number โ€” Header row height override
emptyMessage string โ€” Message when no rows match
labels Partial<GridLabels> en-US i18n string overrides
enableSorting boolean true Enable column sorting
enableFiltering boolean true Enable filter row
enableGrouping boolean false Enable row grouping
grouping { groupBy?: string[]; startCollapsed?: boolean } โ€” Grouping configuration
enableColumnMoving boolean false Enable drag-and-drop column reorder
enablePinning boolean โ€” Enable column pinning (freeze left/right)
enableVirtualization boolean auto virtual scroll (auto at 40+ rows)
virtualizationThreshold number 40 Row count that triggers virtualization
enablePagination boolean false Enable pagination
enablePaginationControls boolean true Show pagination UI
useExternalPagination boolean false Grid skips local slicing
paginationPageSize number โ€” Rows per page
paginationPageSizes number[] | null โ€” Page size selector options
paginationCurrentPage number โ€” Initial page
totalItems number โ€” Total for external pagination
enableExpandable boolean false Enable expandable detail rows
expandableRowHeight number 150 Detail row height
expandableRowHeaderWidth number โ€” Width of the expand toggle column
expandableRowTemplate TemplateRef โ€” Angular template for detail content
expandableRowScope Record<string, unknown> โ€” Extra template context variables
enableTreeView boolean false Enable hierarchical tree display
treeChildrenField string 'children' Property name for child rows
treeIndent number 10 Pixels per indent level
showTreeExpandNoChildren boolean true Show toggle for childless rows
treeRowHeaderAlwaysVisible boolean false Always show expand column
enableCellEdit boolean false Enable inline cell editing
enableCellEditOnFocus boolean false Enter edit mode on cell focus
cellEditableCondition boolean | (ctx) => boolean โ€” Grid-level edit guard
enableAutoResize boolean false ResizeObserver-driven viewport height
infiniteScrollRowsFromEnd number โ€” Threshold rows from end to trigger load
infiniteScrollUp boolean โ€” Enable upward infinite scroll
infiniteScrollDown boolean โ€” Enable downward infinite scroll
rowIdentity (row, i) => string โ€” Custom row ID function
benchmark { iterations?: number } โ€” Render benchmark configuration
onRegisterApi (api) => void โ€” Callback to receive the UiGridApi instance

GridColumnDef

Field Type Description
name string Column identifier (also the default data key)
displayName string Header label (auto-titleized from name if absent)
field string Dot-path into data record (e.g. 'address.city')
type 'string' | 'number' | 'boolean' | 'date' | 'object' Data type hint for sorting and editing
width string CSS grid track value (e.g. '200px', 'minmax(10rem, 1fr)')
align 'start' | 'center' | 'end' Cell text alignment
visible boolean Hide column when false
sortable boolean Column-level sort override
enableSorting boolean Alias for sortable
filterable boolean Column-level filter override
enableFiltering boolean Alias for filterable
enableGrouping boolean Allow grouping by this column
enablePinning boolean Allow pinning this column
pinnedLeft boolean Pin this column to the left on init
pinnedRight boolean Pin this column to the right on init
enableCellEdit boolean Column-level edit override
enableCellEditOnFocus boolean Edit starts on focus for this column
cellEditableCondition boolean | (ctx) => boolean Column-level edit guard
editModelField string Dot-path for writing edited values
sort { direction?: SortDirection; priority?: number; ignoreSort?: boolean } Initial sort configuration
filter { term?: unknown; condition?: FilterCondition | RegExp | Function; flags?: { caseSensitive?: boolean; date?: boolean }; rawTerm?: boolean; noTerm?: boolean } Filter configuration
sortingAlgorithm (a, b) => number Custom sort comparator
formatter (value, row) => string Display value formatter
valueGetter (row) => unknown Custom value extractor
cellTemplate TemplateRef Angular template for cell content
cellRenderer (ctx) => string HTML string cell renderer

UiGridApi

Access the API via onRegisterApi. The API is organized into namespaces.

core

Method Description
refresh() Force a full pipeline re-run
queueGridRefresh() Alias for refresh()
queueRefresh() Alias for refresh()
refreshRows() Alias for refresh()
getVisibleRows() Get currently visible GridRow objects
sortColumn(name, direction?) Programmatic sort
setFilter(name, value) Set a column filter
clearAllFilters() Clear all active filters
groupByColumn(name) Add/remove a column from grouping
clearGrouping() Remove all grouping
moveColumn(from, to) Reorder columns by index
exportCsv() Download visible rows as CSV
benchmark(iterations?) Run render benchmark
setRowInvisible(row, reason?) Hide a row programmatically
clearRowInvisible(row, reason?) Un-hide a row

core.on (Events)

Event Callback Signature
renderingComplete (api) => void
sortChanged (columnName, direction) => void
filterChanged (filters) => void
groupingChanged (groupBy) => void
columnOrderChanged (order) => void
rowsVisibleChanged (rows) => void
rowsRendered (rows) => void
scrollBegin / scrollEnd () => void
canvasHeightChanged (oldHeight, newHeight) => void
gridDimensionChanged (oldH, oldW, newH, newW) => void
benchmarkComplete (result) => void

pagination

Method Description
nextPage() / previousPage() Navigate pages
seek(page) Jump to a specific page
setPageSize(n) Change rows per page
getPage() / getTotalPages() Current page info
getFirstRowIndex() / getLastRowIndex() Row index range
on.paginationChanged (page, pageSize) => void

expandable

Method Description
toggleRowExpansion(row) Toggle a single row
expandAllRows() / collapseAllRows() Bulk expand/collapse
toggleAllRows() Toggle all rows
on.rowExpandedStateChanged (row, expanded) => void

treeBase

Method Description
toggleRowTreeState(row) Toggle tree node expansion
expandRow(row) / collapseRow(row) Explicit expand/collapse
expandAllRows() / collapseAllRows() Bulk operations
getRowChildren(row) Get child GridRow objects
on.rowExpanded / on.rowCollapsed (row) => void

treeView

Method Description
getTreeView() Get current tree expansion state
setTreeView(state) Bulk set expansion state

pinning

Method Description
pinColumn(name, direction) Pin a column left, right, or unpin ('none')
on.columnPinned (columnName, direction) => void

infiniteScroll

Method Description
dataLoaded(scrollUp?, scrollDown?) Signal that new data is loaded
resetScroll(scrollUp?, scrollDown?) Reset to fresh state
saveScrollPercentage() Persist scroll position ratio
setScrollDirections(up, down) Toggle scroll directions
dataRemovedTop(scrollUp?, scrollDown?) Signal rows removed from top
dataRemovedBottom(scrollUp?, scrollDown?) Signal rows removed from bottom
on.needLoadMoreData () => void
on.needLoadMoreDataTop () => void

edit

Method / Event Description
beginCellEdit(row, col, event?) Start editing a cell
endCellEdit() Commit the current edit
cancelCellEdit() Cancel without committing
getEditingCell() Returns current editing position or null
on.beginCellEdit (row, col, event) => void
on.afterCellEdit (row, col, newVal, oldVal) => void
on.cancelCellEdit (row, col) => void

saveState

Method Description
save() Returns a serializable GridSavedState
restore(state) Applies a previously saved state

Cell Editing (`docs/cell-editing.md`)

Cell Editing

Inline spreadsheet-style editing with full keyboard navigation.

Enable Editing

Set enableCellEdit or enableCellEditOnFocus at the grid or column level:

typescript
const gridOptions: GridOptions = {
  enableCellEditOnFocus: true, // grid-level: edit on cell focus
  columnDefs: [
    { name: 'name', enableCellEdit: true },    // this column is editable
    { name: 'revenue', enableCellEdit: false }, // this column is read-only
  ],
};

Keyboard Navigation

Key Action
F2 or Enter Begin editing focused cell
Any printable key Begin editing and replace value
Backspace / Delete Clear value and begin editing
Tab Commit and move to next editable cell
Shift + Tab Commit and move to previous editable cell
Enter Commit and move down
Shift + Enter Commit and move up
Escape Cancel edit, restore original value

Conditional Editing

Use cellEditableCondition to allow/deny editing per row:

typescript
{
  name: 'status',
  enableCellEdit: true,
  cellEditableCondition: (ctx) => ctx.row['status'] !== 'Enterprise',
}

Edit Model Field

When the column name differs from the data property you want to write to, set editModelField:

typescript
{
  name: 'owner',
  field: 'account.owner',
  editModelField: 'account.owner', // writes edited value to this path
  enableCellEdit: true,
}

API

Method / Event Description
gridApi.edit.beginCellEdit(row, colName) Start editing programmatically
gridApi.edit.endCellEdit() Commit the current edit
gridApi.edit.cancelCellEdit() Cancel without committing
gridApi.edit.getEditingCell() Returns the current editing position or null
gridApi.edit.on.beginCellEdit (row, col, event) => void
gridApi.edit.on.afterCellEdit (row, col, newVal, oldVal) => void
gridApi.edit.on.cancelCellEdit (row, col) => void

Type-Aware Editing

The editor input type adapts to the column's type:

  • 'number' โ†’ ``
  • 'date' โ†’ ``
  • default โ†’ ``

Value parsing is automatic: numeric strings become Number, 'true'/'false' become booleans, and date strings remain as-is.


Custom Builds (`docs/custom-builds.md`)

Custom Builds

Ship only the features you use. The build script generates compile-time boolean constants that let the bundler tree-shake unused code paths entirely.

Feature Flags

CLI Name TypeScript Constant What It Guards
sorting FEATURE_SORTING Column sort UI and pipeline sort step
filtering FEATURE_FILTERING Filter row and pipeline filter step
grouping FEATURE_GROUPING Group headers and grouping pipeline
pagination FEATURE_PAGINATION Pagination footer and page slicing
cell-edit FEATURE_CELL_EDIT Inline editing UI and keyboard handlers
expandable FEATURE_EXPANDABLE Expandable detail rows
tree-view FEATURE_TREE_VIEW Tree row rendering and tree pipeline
infinite-scroll FEATURE_INFINITE_SCROLL Infinite scroll state machine
column-moving FEATURE_COLUMN_MOVING drag-and-drop on headers
csv-export FEATURE_CSV_EXPORT CSV export and toolbar button
save-state FEATURE_SAVE_STATE Save/restore state serialization
auto-resize FEATURE_AUTO_RESIZE ResizeObserver integration

Usage

bash
# Full build (default โ€” all features enabled)
node scripts/build-grid.mjs

# Minimal build โ€” only sorting and filtering
node scripts/build-grid.mjs --features sorting,filtering

# Custom locale baked in at build time
node scripts/build-grid.mjs --locale projects/ui-grid/src/lib/grid/i18n/fr-FR.json

# Both features and locale
node scripts/build-grid.mjs --features sorting,filtering,pagination --locale i18n/fr-FR.json

# See available flags
node scripts/build-grid.mjs --list

# Dry run โ€” writes generated file but skips ng build
node scripts/build-grid.mjs --features sorting --dry-run

How It Works

  1. The script backs up grid.features.ts
  2. Generates a new version with only selected flags set to true
  3. Optionally swaps i18n/en-US.json with the provided locale file
  4. Runs ng build uiGridPackage --configuration production
  5. Restores the original files (even on build failure)

The bundler sees if (false) { ... } and tree-shakes the dead code path entirely. The grid API uses noop fallbacks for all optional feature namespaces, so runtime behavior is always safe.

Build Presets

Higher-level presets are available via the preset build script:

bash
# Build the full preset (default)
npm run build:library:preset -- --preset=full

# Build the minimal/headless preset
npm run build:library:preset -- --preset=minimal
Preset Includes Status
full All features + Angular component Supported
minimal Headless core: API, sorting, filtering, pagination Supported
data-heavy Sorting, filtering, viewport, export Planned
interactive Sorting, filtering, grouping, editing, tree, expandable Planned

Expandable Rows (`docs/expandable-rows.md`)

Expandable Rows

Master/detail pattern โ€” expand any row to reveal a custom Angular template below it.

Setup

typescript
@Component({
  template: `
    <ng-template #detail let-row>
      <div class="detail-panel">
        <h3>{{ row.name }}</h3>
        <p>Revenue: {{ row.revenue | currency }}</p>
      </div>
    </ng-template>

    <app-ui-grid [options]="gridOptions" />
  `
})
export class MyComponent {
  @ViewChild('detail') detailTemplate!: TemplateRef<any>;

  gridOptions: GridOptions = {
    id: 'master-detail',
    data: this.data,
    enableExpandable: true,
    expandableRowHeight: 120,
    expandableRowTemplate: this.detailTemplate,
    columnDefs: [
      { name: 'name' },
      { name: 'revenue', type: 'number' },
    ],
  };
}

Options

Option Type Default Description
enableExpandable boolean false Enable expandable rows
expandableRowHeight number 150 Min height of the detail row (px)
expandableRowTemplate TemplateRef โ€” Angular template for the detail content
expandableRowHeaderWidth number โ€” Width of the expand toggle column
expandableRowScope Record<string, unknown> โ€” Extra variables injected into template context

Template Context

The template receives a GridExpandableTemplateContext:

Property Type Description
$implicit GridRecord The row data (use with let-row)
row GridRecord Same as $implicit
rowIndex number Index of the row in the current view
expanded boolean Always true when the template renders

API

Method Description
gridApi.expandable.toggleRowExpansion(row) Toggle a single row
gridApi.expandable.expandAllRows() Expand all rows
gridApi.expandable.collapseAllRows() Collapse all rows
gridApi.expandable.toggleAllRows() Toggle all rows
gridApi.expandable.on.rowExpandedStateChanged (row, expanded) => void

Expandable vs Tree View

Expandable rows show custom template content below a data row (master/detail). Tree view shows child data rows that share the same column structure as parents. You can use both simultaneously.


Features (`docs/features.md`)

Features

Every feature is included free and open source. No enterprise tier, no license keys. Features can be individually tree-shaken at build time via compile-time feature flags.

Feature Overview

Feature Option Flag Default Description
Sorting enableSorting true Click column headers to cycle asc / desc / none
Filtering enableFiltering true Per-column filter inputs with configurable conditions
Row Grouping enableGrouping false Nested multi-column grouping with collapsible headers
Tree View enableTreeView false Hierarchical data with expand/collapse per node
Expandable Rows enableExpandable false Master/detail pattern with custom Angular templates
Cell Editing enableCellEdit false Inline spreadsheet-style editing with keyboard nav
Pagination enablePagination false Client-side or external pagination
Infinite Scroll infiniteScrollRowsFromEnd โ€” Bi-directional infinite scroll with loading state
Column Moving enableColumnMoving false Drag-and-drop column reordering
CSV Export gridApi.core.exportCsv() โ€” Export visible rows with formula-injection protection
Virtual Scrolling enableVirtualization auto virtual scroll, auto-enabled at 40+ rows
Save/Restore State gridApi.saveState โ€” Serialize sort, filter, grouping, pagination, expansion state
Auto Resize enableAutoResize false ResizeObserver-driven viewport height recalculation
Custom Templates cellTemplate โ€” Angular ng-template for fully custom cell rendering
Shadow DOM always โ€” Encapsulated styles with CSS custom property and ::part() hooks
Web Component npm run build:vanilla โ€” Ship as `` for package or no-bundler browser usage
SSR Support automatic โ€” Server-side rendering with platform-safe guards
i18n labels en-US Override any UI string at runtime or bake in a locale at build time

Sorting

  • Enabled by default (enableSorting: true)
  • Click a column header to cycle: none โ†’ ascending โ†’ descending โ†’ none
  • Per-column disable: column.sortable = false
  • Custom comparator: column.sortingAlgorithm = (a, b) => number
  • Programmatic: gridApi.core.sortColumn('name', 'asc')

Filtering

  • Enabled by default (enableFiltering: true)
  • Built-in conditions: contains, startsWith, endsWith, exact, notEqual, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual
  • Wildcard support: terms with * auto-compile to regex
  • Custom predicates: column.filter.condition = (term, value, row, column) => boolean
  • RegExp conditions: column.filter.condition = /pattern/i

Row Grouping

  • Requires enableGrouping: true
  • Multiple columns: grouping: { groupBy: ['status', 'company'] }
  • Start collapsed: grouping: { startCollapsed: true }
  • Per-column disable: column.enableGrouping = false
  • Mutually exclusive with Tree View

Compile-Time Feature Flags

Build custom bundles with only the features you need:

bash
node scripts/build-grid.mjs --features sorting,filtering

Disabled features compile to false constants โ€” the bundler tree-shakes all guarded code paths. See Custom Builds for details.


I18n (`docs/i18n.md`)

Internationalization

Override any UI string at runtime via the labels option, or bake in a complete locale file at build time.

Default Labels (en-US)

Key Default Value
sortDefault Sort
sortAsc Sort ascending
sortDesc Sort descending
groupColumn Group by this column
ungroupColumn Ungroup this column
groupCollapse Collapse group
groupExpand Expand group
treeCollapse Collapse
treeExpand Expand
expandDetail Expand row
collapseDetail Collapse row
filterPlaceholder Filter...
filterDisabled Filter disabled
filterColumn Filter this column
paginationPrevious Previous
paginationNext Next
paginationPage Page
paginationOf of
paginationRows rows
emptyHeading No data
emptyDescription No rows to display
toolbarOf of
toolbarRows rows
statsVisibleRows visible rows
groupRowsSuffix rows

Runtime Override

Pass partial overrides via GridOptions.labels:

typescript
const gridOptions: GridOptions = {
  id: 'my-grid',
  data: myData,
  columnDefs: myColumns,
  labels: {
    filterPlaceholder: 'Filtrer...',
    emptyHeading: 'Aucune donnรฉe',
    emptyDescription: 'Aucune ligne ร  afficher',
    paginationPrevious: 'Prรฉcรฉdent',
    paginationNext: 'Suivant',
  },
};

Build-Time Locale

Bake in a complete locale file at build time. This replaces en-US.json during the build:

bash
node scripts/build-grid.mjs --locale projects/ui-grid/src/lib/grid/i18n/fr-FR.json

Creating a Locale File

  1. Copy projects/ui-grid/src/lib/grid/i18n/en-US.json
  2. Translate all 26 keys
  3. Save as a new JSON file (e.g. fr-FR.json)
  4. The file must use the same key names โ€” only the values change

ARIA Labels and Screen Readers

All interactive elements in the grid derive their aria-label values from the GridLabels object. When you provide translated labels, screen reader announcements localize automatically โ€” no additional configuration is needed.

Labels that flow into ARIA attributes:

Label Key ARIA Usage
sortDefault / sortAsc / sortDesc aria-label on sort buttons
groupColumn / ungroupColumn aria-label on group toggle buttons
groupCollapse / groupExpand Screen reader text on group disclosure rows
treeCollapse / treeExpand aria-label on tree toggle buttons
expandDetail / collapseDetail aria-label on expand/collapse detail buttons
filterColumn Screen reader text prefix for filter inputs ("Filter {column}")
paginationPrevious / paginationNext aria-label on pagination buttons
paginationPage aria-label on the pagination <nav> element

See the Accessibility guide for the full a11y documentation.


Theming (`docs/theming.md`)

Theming

UI Grid uses Shadow DOM encapsulation. All visual customization flows through CSS custom properties and ::part() selectors.

The runtime styles now normalize theme resolution around the public --ui-grid-* token names again. Legacy --app-ui-grid-* aliases still work as a fallback, but consumer themes should target the public --ui-grid-* surface.

How It Works

The grid component renders inside a Shadow DOM boundary. Parent styles cannot leak in. Instead, the grid exposes two theming surfaces:

  1. CSS custom properties โ€” set --ui-grid-* variables on any ancestor element. They pierce the shadow boundary. Legacy --app-ui-grid-* aliases remain supported as fallback input for older app themes.
  2. ::part() selectors โ€” target specific structural elements by their part name for full CSS access.

All public styling tokens follow the --ui-grid-* naming pattern.

css
.my-app {
  --ui-grid-surface: #1e1b2e;
  --ui-grid-accent: #8b5cf6;
  --ui-grid-header-background: #2d2640;
  --ui-grid-cell-color: #e2e0f0;
  --ui-grid-border-color: rgba(139, 92, 246, 0.2);
  --ui-grid-row-hover: #322e4a;
}

If you already ship older --app-ui-grid-* theme tokens, they still work as a compatibility fallback. New consumer themes should use only --ui-grid-*.

CSS Custom Properties

Foundation

Variable Default Controls
--ui-grid-surface #ffffff Base surface for the frame, controls, menus, and inputs
--ui-grid-border-color #d4d4d8 Shared border color across frame, cells, controls, and menus
--ui-grid-header-background #f3f4f6 Header and filter row background
--ui-grid-header-weight 700 Header label font weight
--ui-grid-cell-color #111827 Primary text color for rows and controls
--ui-grid-muted-color #6b7280 Secondary text color for helper copy and inactive controls
--ui-grid-row-odd #fcfcfd Odd striped row background
--ui-grid-row-even #f7f7f8 Even striped row background
--ui-grid-row-hover #eef4ff Row hover background
--ui-grid-accent #2563eb Accent color for active states, focus rings, and highlighted controls
--ui-grid-group-background #eceff3 Group header row background
--ui-grid-radius 4px Base corner radius reused by frame, cards, and inputs
--ui-grid-shadow 0 10px 24px rgba(15, 23, 42, 0.08) Frame shadow

Status Pills

Variable Default Controls
--ui-grid-status-active-bg rgba(22, 163, 74, 0.14) Active pill background
--ui-grid-status-active-color #166534 Active pill text
--ui-grid-status-expansion-bg rgba(37, 99, 235, 0.14) Expansion pill background
--ui-grid-status-expansion-color #1d4ed8 Expansion pill text
--ui-grid-status-enterprise-bg rgba(15, 118, 110, 0.14) Enterprise pill background
--ui-grid-status-enterprise-color #115e59 Enterprise pill text
--ui-grid-status-pilot-bg rgba(234, 88, 12, 0.14) Pilot pill background
--ui-grid-status-pilot-color #c2410c Pilot pill text
--ui-grid-status-pill-padding-block 0.2rem Vertical pill padding
--ui-grid-status-pill-padding-inline 0.55rem Horizontal pill padding
--ui-grid-status-pill-radius 999px Pill border radius
--ui-grid-status-pill-font-size 0.85rem Pill font size
--ui-grid-status-pill-default-bg rgba(15, 23, 42, 0.07) Default pill background when no named state class is applied

Shell And Hero

Variable Default Controls
--ui-grid-shell-gap 1.5rem Gap between major shell sections
--ui-grid-hero-gap 1.5rem Gap between hero content and actions
--ui-grid-hero-padding-block 1rem Hero top and bottom padding
--ui-grid-eyebrow-margin-block-end 0.5rem Space under the eyebrow label
--ui-grid-eyebrow-letter-spacing 0.18em Eyebrow tracking
--ui-grid-eyebrow-font-size 0.72rem Eyebrow size
--ui-grid-hero-title-font-size clamp(1.4rem, 2vw, 2rem) Hero title size
--ui-grid-hero-title-line-height 1.1 Hero title line height
--ui-grid-hero-deck-max-width 56ch Max width for the intro paragraph
--ui-grid-hero-deck-margin-block-start 0.75rem Space above the deck paragraph
--ui-grid-hero-actions-gap 1rem Gap between hero actions
--ui-grid-action-radius 999px CTA button radius
--ui-grid-action-padding-block 0.8rem CTA button vertical padding
--ui-grid-action-padding-inline 1.1rem CTA button horizontal padding
--ui-grid-stats-card-min-width 8rem Minimum width for hero stats cards
--ui-grid-stats-card-padding-block 0.85rem Stats card vertical padding
--ui-grid-stats-card-padding-inline 1rem Stats card horizontal padding
--ui-grid-stats-card-value-font-size 1.9rem Hero stats numeric size

Frame, Metrics, Toolbar, And Scrolling

Variable Default Controls
--ui-grid-metrics-gap 1rem Gap between metric cards
--ui-grid-metric-card-padding-block 1rem Metric card vertical padding
--ui-grid-metric-card-padding-inline 1.1rem Metric card horizontal padding
--ui-grid-metric-value-font-size 1.3rem Metric value size
--ui-grid-toolbar-gap 1rem Gap inside the toolbar
--ui-grid-toolbar-padding-block 1rem Toolbar vertical padding
--ui-grid-toolbar-padding-inline 1.25rem Toolbar horizontal padding
--ui-grid-toolbar-inline-gap 0.4rem Inline gap in toolbar text blocks
--ui-grid-overflow-x auto Horizontal overflow behavior for the single grid scroll container
--ui-grid-overflow-y auto Vertical overflow behavior for the single grid scroll container

Header, Pinning, And Controls

Variable Default Controls
--ui-grid-header-z-index 3 Sticky header and filter row stacking level
--ui-grid-pinned-cell-z-index 2 Sticky pinned cell stacking level
--ui-grid-pin-menu-open-z-index 8 Header cell stacking level when the inline pin chooser is open
--ui-grid-pin-menu-z-index 20 Pin chooser internal stacking level
--ui-grid-header-cell-gap 0.75rem Gap between header label and action cluster
--ui-grid-header-cell-padding-block 0.85rem Header cell vertical padding
--ui-grid-header-cell-padding-inline 1rem Header cell horizontal padding
--ui-grid-header-cell-background-active color-mix(in srgb, var(--ui-grid-accent) 8%, var(--ui-grid-header-background)) Background for an active sorted header cell
--ui-grid-pinned-divider-shadow-left 2px 0 4px rgba(0, 0, 0, 0.06) Shadow on the trailing edge of left-pinned columns
--ui-grid-pinned-divider-shadow-right -2px 0 4px rgba(0, 0, 0, 0.06) Shadow on the leading edge of right-pinned columns
--ui-grid-pinned-divider-clip-left inset(0 -4px 0 0) Clip path for the left pinned divider shadow
--ui-grid-pinned-divider-clip-right inset(0 0 0 -4px) Clip path for the right pinned divider shadow
--ui-grid-header-actions-gap 0.4rem Gap between sort, group, and pin actions
--ui-grid-control-size 2rem Shared square size for header action chips
--ui-grid-control-radius 999px Shared control radius
--ui-grid-control-icon-size 1rem Icon size for action chips and pin arrows
--ui-grid-control-background var(--ui-grid-surface) Base control background
--ui-grid-control-border-color var(--ui-grid-border-color) Base control border color
--ui-grid-control-color var(--ui-grid-muted-color) Base control icon and text color
--ui-grid-control-background-active color-mix(in srgb, var(--ui-grid-accent) 12%, var(--ui-grid-surface)) Active control background
--ui-grid-control-border-color-active color-mix(in srgb, var(--ui-grid-accent) 35%, var(--ui-grid-border-color)) Active control border
--ui-grid-control-color-active var(--ui-grid-accent) Active control icon and text color
--ui-grid-pin-menu-gap 0.25rem Gap between inline pin chooser arrows
--ui-grid-pin-menu-padding 0.25rem Padding inside the inline pin chooser
--ui-grid-pin-menu-radius 999px Inline pin chooser radius
--ui-grid-pin-menu-shadow 0 10px 24px color-mix(in srgb, var(--ui-grid-cell-color) 10%, transparent) Inline pin chooser shadow
--ui-grid-pin-menu-action-size 1.75rem Size of each left or right pin action
--ui-grid-pin-control-collapsed-size 1px Final collapsed size of the pin button while the chooser opens
--ui-grid-pin-control-transition-duration 160ms Duration for the inline pin control animation
--ui-grid-pin-control-transition-easing cubic-bezier(0.22, 1, 0.36, 1) Easing curve for pin control motion
--ui-grid-pin-menu-scale-closed 0.72 Closed-state scale used before the chooser expands in place

Filters

Variable Default Controls
--ui-grid-filter-cell-padding-block-start 0.75rem Filter row top padding
--ui-grid-filter-cell-padding-block-end 1rem Filter row bottom padding
--ui-grid-filter-cell-padding-inline 1rem Filter row horizontal padding
--ui-grid-filter-input-padding-block 0.55rem Filter input vertical padding
--ui-grid-filter-input-padding-inline 0.7rem Filter input horizontal padding
--ui-grid-filter-input-focus-outline 2px solid color-mix(in srgb, var(--ui-grid-accent) 18%, transparent) Focus outline for filter inputs

Rows, Cells, And Editing

Variable Default Controls
--ui-grid-body-cell-padding-block 0.95rem Body cell vertical padding
--ui-grid-body-cell-padding-inline 1rem Body cell horizontal padding
--ui-grid-cell-shell-gap 0.55rem Gap between toggles and content inside a cell
--ui-grid-row-toggle-size 1.55rem Tree and expandable toggle size
--ui-grid-row-toggle-radius 999px Tree and expandable toggle radius
--ui-grid-toggle-icon-size 1.1rem General toggle icon size
--ui-grid-group-disclosure-icon-size 0.9rem Group row disclosure icon size
--ui-grid-pagination-icon-size 1.2rem Pagination icon size
--ui-grid-expandable-row-padding-block 1rem Expanded row vertical padding
--ui-grid-expandable-row-padding-inline 1.25rem Expanded row horizontal padding
--ui-grid-expandable-row-background color-mix(in srgb, var(--ui-grid-accent) 4%, var(--ui-grid-surface)) Expanded row background
--ui-grid-cell-focus-background color-mix(in srgb, var(--ui-grid-accent) 10%, var(--ui-grid-surface)) Focused cell background
--ui-grid-cell-focus-ring inset 0 0 0 1px color-mix(in srgb, var(--ui-grid-accent) 28%, transparent) Focus ring for keyboard-focused cells
--ui-grid-cell-editing-background color-mix(in srgb, var(--ui-grid-accent) 7%, var(--ui-grid-surface)) Editing cell background
--ui-grid-cell-editor-border-color color-mix(in srgb, var(--ui-grid-accent) 45%, var(--ui-grid-border-color)) Editor border color
--ui-grid-cell-editor-radius calc(var(--ui-grid-radius) - 1px) Inline cell editor radius
--ui-grid-cell-editor-padding-block 0.45rem Editor vertical padding
--ui-grid-cell-editor-padding-inline 0.55rem Editor horizontal padding
--ui-grid-cell-editor-focus-outline 2px solid color-mix(in srgb, var(--ui-grid-accent) 18%, transparent) Editor focus outline

Group Rows And Drag States

Variable Default Controls
--ui-grid-group-row-gap 0.9rem Gap inside group rows
--ui-grid-group-row-padding-block 0.9rem Group row vertical padding
--ui-grid-group-row-padding-inline 1rem Group row horizontal padding
--ui-grid-drag-preview-shadow 0 12px 30px rgba(15, 23, 42, 0.16) Drag preview shadow for column reordering
--ui-grid-drag-preview-radius 0.9rem Drag preview radius
--ui-grid-drag-placeholder-opacity 0.3 Placeholder opacity during drag
--ui-grid-drag-placeholder-background color-mix(in srgb, var(--ui-grid-accent) 9%, var(--ui-grid-header-background)) Placeholder background during drag
--ui-grid-drag-placeholder-border 1px dashed color-mix(in srgb, var(--ui-grid-accent) 45%, var(--ui-grid-border-color)) Placeholder border during drag
--ui-grid-drag-placeholder-shadow inset 0 0 0 1px color-mix(in srgb, var(--ui-grid-accent) 18%, transparent) Placeholder shadow during drag

Empty State And Pagination

Variable Default Controls
--ui-grid-empty-state-gap 0.35rem Gap between empty-state lines
--ui-grid-empty-state-min-height 16rem Minimum empty-state height
--ui-grid-empty-state-padding 2rem Empty-state padding
--ui-grid-pagination-gap 1rem Gap in the pagination bar
--ui-grid-pagination-padding-block 0.85rem Pagination bar vertical padding
--ui-grid-pagination-padding-inline 1.25rem Pagination bar horizontal padding
--ui-grid-pagination-controls-gap 0.75rem Gap between pagination controls
--ui-grid-pagination-size-gap 0.4rem Gap between rows-per-page label and select
--ui-grid-pagination-button-padding-block 0.45rem Pagination button vertical padding
--ui-grid-pagination-button-padding-inline 0.8rem Pagination button horizontal padding
--ui-grid-pagination-select-padding-block 0.35rem Pagination select vertical padding
--ui-grid-pagination-select-padding-inline 0.55rem Pagination select horizontal padding

`::part()` Hooks

Part Name Target Element
shell Outermost grid wrapper
hero Title/toolbar area
grid-frame Scrollable grid container
grid-toolbar Toolbar with row count
header Header row container
header-cell Individual header cell
filter-cell Filter input cell
body-cell Data cell
group-row Group header row
expandable-row Expanded detail row
pagination Pagination footer
empty-state Empty state message

Example: Custom Brand Theme

scss
.my-app {
  /* foundation */
  --ui-grid-surface: #1e1b2e;
  --ui-grid-border-color: rgba(139, 92, 246, 0.2);
  --ui-grid-header-background: #2d2640;
  --ui-grid-cell-color: #e2e0f0;
  --ui-grid-muted-color: #8b7fb0;
  --ui-grid-row-odd: #1e1b2e;
  --ui-grid-row-even: #252238;
  --ui-grid-row-hover: #322e4a;
  --ui-grid-accent: #8b5cf6;
  --ui-grid-group-background: #2d2640;

  /* structure */
  --ui-grid-radius: 16px;
  --ui-grid-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  --ui-grid-toolbar-padding-inline: 1.5rem;
  --ui-grid-body-cell-padding-inline: 1.25rem;

  /* controls */
  --ui-grid-control-size: 2.15rem;
  --ui-grid-pin-control-transition-duration: 200ms;
  --ui-grid-pin-menu-action-size: 1.9rem;
}

/* Target specific parts for full CSS access */
.my-app app-ui-grid::part(header) {
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

Host Attribute Pattern

The demo app uses data-color-mode and data-visual-mode attributes on the root component to drive a 2ร—2 theme matrix (dark/light ร— studio/wireframe). Each combination defines a full set of semantic CSS tokens that cascade into the grid:

scss
:host([data-color-mode='dark'][data-visual-mode='default']) {
  --grid-surface: #0b1824;
  --grid-header: #112434;
  --grid-text: #ebf5f9;
  --grid-accent: #67e8f9;
  /* ...then map to --ui-grid-* on the grid container */
}

You can use this same pattern in your own app to support multiple themes without JavaScript logic. If you already have legacy --app-ui-grid-* aliases in your codebase, you can leave them in place while standardizing new theme work on the public --ui-grid-* names.


Tree View (`docs/tree-view.md`)

Tree View

Display hierarchical data with collapsible parent/child rows.

Data Shape

Nest child rows under a configurable property (default: children):

typescript
const treeData = [
  {
    name: 'Engineering',
    headcount: 42,
    children: [
      { name: 'Frontend', headcount: 18 },
      { name: 'Backend', headcount: 24 },
    ],
  },
];

const gridOptions: GridOptions = {
  id: 'org-tree',
  data: treeData,
  enableTreeView: true,
  treeChildrenField: 'children',
  treeIndent: 20,
  columnDefs: [
    { name: 'name' },
    { name: 'headcount', type: 'number' },
  ],
};

Options

Option Type Default Description
enableTreeView boolean false Enable tree mode
treeChildrenField string 'children' Property name for child array
treeIndent number 10 Pixels per indent level
showTreeExpandNoChildren boolean true Show toggle for childless rows
treeRowHeaderAlwaysVisible boolean false Always show expand column

Filtering

When a filter is active, child matches bubble up โ€” parent rows are shown even when they don't match the filter, as long as at least one child does.

API

Method Description
gridApi.treeBase.toggleRowTreeState(row) Toggle node expansion
gridApi.treeBase.expandRow(row) Expand a node
gridApi.treeBase.collapseRow(row) Collapse a node
gridApi.treeBase.expandAllRows() Expand all nodes
gridApi.treeBase.collapseAllRows() Collapse all nodes
gridApi.treeBase.getRowChildren(row) Get child GridRow objects
gridApi.treeView.getTreeView() Get current expansion state map
gridApi.treeView.setTreeView(state) Bulk set expansion state

Tree View vs Grouping

Tree View Row Grouping
Data source Pre-structured with children array Flat data โ€” groups created dynamically
Nesting Your data defines the hierarchy Grid creates groups from column values
Mutual exclusivity Disables grouping when active Disables tree view when active

Web Component (`docs/web-component.md`)

Web Component Output (`@ornery/ui-grid-vanilla`)

A framework-free custom element built on @ornery/ui-grid-core with pure DOM rendering and Shadow DOM encapsulation. The package entry points are designed for package-aware tools, and the browser bundle is designed for direct static HTML usage.

Install

bash
npm install @ornery/ui-grid-vanilla @ornery/ui-grid-core

Declarative HTML Usage

This is the non-Angular custom element build. Use it in static sites, plain JavaScript apps, or any host that should not carry Angular runtime code.

For a no-bundler static host, use the browser bundle. It includes the core runtime and auto-registers `` when loaded:

html
<script type="module" src="./ui-grid-element.js"></script>

<ui-grid-element
  grid-id="vanilla-demo"
  title="Team Roster"
  enable-sorting
  enable-filtering
  column-defs='[
    { "name": "name" },
    { "name": "role" },
    { "name": "salary", "type": "number", "align": "end" }
  ]'
  data='[
    { "name": "Alice", "role": "Engineer", "salary": 120000 },
    { "name": "Bob", "role": "Designer", "salary": 95000 }
  ]'
>
</ui-grid-element>

After npm run build:vanilla, the file is available at projects/ui-grid-vanilla/dist/browser/ui-grid-element.js. Copy or serve that file with your static app. The package dist/index.js file is CommonJS, and dist/index.mjs is package ESM that still expects package resolution; neither is the direct browser drop-in file.

The vanilla element supports the same declarative attribute surface. Small and medium datasets can be configured directly in markup with zero grid setup JavaScript beyond registration:

html
<ui-grid-element
  grid-id="vanilla-demo"
  title="Team Roster"
  enable-sorting
  enable-filtering
  column-defs='[
    { "name": "name" },
    { "name": "role" },
    { "name": "salary", "type": "number", "align": "end" }
  ]'
  data='[
    { "name": "Alice", "role": "Engineer", "salary": 120000 },
    { "name": "Bob", "role": "Designer", "salary": 95000 }
  ]'
>
</ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement(); // registers <ui-grid-element>
</script>

Supported declarative inputs include:

  • Boolean attributes such as enable-sorting, enable-filtering, enable-grouping, enable-pinning, enable-pagination, and enable-virtualization
  • JSON attributes such as column-defs, data, grouping, and pagination-page-sizes

Use the declarative surface for static pages, docs, CMS-rendered content, and simple embeds. For callbacks like onRegisterApi or function-based column logic, use the options property.

Performance Guidance

  • Best use for declarative attributes: initial mount, static markup-first pages, and infrequent option changes.
  • Cost of declarative updates: changing JSON attributes such as data or column-defs forces the element to parse the new payload, merge options again, and may trigger more DOM work than necessary for live feeds.
  • Best use for imperative updates: callbacks, one-shot bulk configuration, host-framework integration, and any case where application code already owns the current row set.
  • High-frequency updates: for streaming or trading-style feeds, mount declaratively if you want, but switch live row updates to grid.setData(rows) instead of replacing the data attribute. setData(rows) is the vanilla element's cheapest update path because it patches the existing grid surface instead of re-running the full declarative sync flow.

JavaScript Property Usage

The vanilla element also exposes individual JS properties that mirror the declarative attributes. This is useful when a framework or host app wants property binding without constructing a full options object:

html
<ui-grid-element id="my-grid"></ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement();

  const grid = document.querySelector('#my-grid');
  grid.gridId = 'vanilla-props';
  grid.title = 'Team Roster';
  grid.enableSorting = true;
  grid.enableFiltering = true;
  grid.columnDefs = [
    { name: 'name' },
    { name: 'role' },
    { name: 'salary', type: 'number', align: 'end' },
  ];
  grid.data = [
    { name: 'Alice', role: 'Engineer', salary: 120000 },
    { name: 'Bob', role: 'Designer', salary: 95000 },
  ];
  grid.paginationPageSizes = [10, 25, 50];
</script>

Full `options` Usage

html
<ui-grid-element id="my-grid"></ui-grid-element>

<script type="module">
  import { defineStandaloneUiGridElement } from '@ornery/ui-grid-vanilla';

  await defineStandaloneUiGridElement(); // registers <ui-grid-element>

  document.querySelector('#my-grid').options = {
    id: 'vanilla-demo',
    data: [
      { name: 'Alice', role: 'Engineer', salary: 120000 },
      { name: 'Bob', role: 'Designer', salary: 95000 },
    ],
    columnDefs: [
      { name: 'name' },
      { name: 'role' },
      { name: 'salary', type: 'number', align: 'end' },
    ],
  };
</script>

Mount Helper

The mountVanillaUiGrid helper registers the element, creates it, sets options, and appends it to a target element in one call:

typescript
import { mountVanillaUiGrid } from '@ornery/ui-grid-vanilla';

const grid = await mountVanillaUiGrid(document.getElementById('app'), {
  id: 'mounted-grid',
  data: myData,
  columnDefs: myColumns,
});

To use the optional WASM engine with the vanilla element, install and pass the compiled module published by the Rust project:

bash
npm install @ornery/ui-grid-wasm
typescript
import { mountVanillaUiGrid } from '@ornery/ui-grid-vanilla';
import * as wasmModule from '@ornery/ui-grid-wasm';

const grid = await mountVanillaUiGrid(
  document.getElementById('app'),
  { id: 'wasm-grid', data: myData, columnDefs: myColumns },
  wasmModule,
);

If no WASM module is provided, or if WASM initialization fails, the grid automatically falls back to the JavaScript engine.

Slot Templates

The vanilla element supports <template> elements with slot attributes for custom cell and expandable row content. Templates use {{ expression }} interpolation:

html
<ui-grid-element id="my-grid">
  <template slot="cell-name">
    <strong>{{ value }}</strong>
  </template>
  <template slot="expandable-row">
    <p>Details for {{ row.name }}</p>
  </template>
</ui-grid-element>

State API

The vanilla element exposes getState() and setState() methods for save/restore:

javascript
const state = grid.getState(); // returns GridSaveState | null
grid.setState(state); // applies a previously saved state

Shared: Setting Options

Shared Grid Configuration Surface

Both outputs accept the same GridOptions object as the Angular component, plus the same declarative attributes for common markup-first setup:

For both elements, the options property is the advanced escape hatch rather than the only configuration path. Prefer attributes when they are sufficient; use options when you need callbacks, function-valued column definitions, or one-shot bulk assignment.

In performance terms, think of the surfaces like this:

  • Declarative attributes optimize for authoring convenience and HTML-first setup.
  • options assignment optimizes for programmatic control and coarse reconfiguration.
  • Vanilla setData(rows) optimizes for repeated row-data refreshes and is the preferred path for high-frequency updates.
javascript
const grid = document.querySelector('ui-grid-element');
grid.options = {
  id: 'my-grid',
  data: myData,
  columnDefs: myColumns,
  enableSorting: true,
  enableFiltering: true,
  onRegisterApi: (api) => {
    window.gridApi = api;
  },
};

Shared: Styling

Both variants use Shadow DOM. Style them via CSS custom properties on an ancestor:

css
.my-container {
  --ui-grid-surface: #1a1a2e;
  --ui-grid-cell-color: #e0e0e0;
  --ui-grid-accent: #00d4aa;
  --ui-grid-border-color: rgba(0, 212, 170, 0.2);
  --ui-grid-header-background: #242440;
}

Shared: Events

Use onRegisterApi in the options to receive the grid API, then subscribe to events:

javascript
grid.options = {
  // ...
  onRegisterApi: (api) => {
    api.core.on.sortChanged((column, direction) => {
      console.log('Sort:', column, direction);
    });

    api.edit.on.afterCellEdit((row, col, newVal, oldVal) => {
      console.log('Edited:', col, oldVal, '->', newVal);
    });
  },
};

See orneryd/ui-grid for Rust implementation and native adapter documentation.


--- METRICS ---