# Platform & Support

> What you can rely on when you depend on @toolbox-web/grid — adapter support matrix, browser baseline, CSP & security handling, server-side rendering, deprecation policy, and release cadence.

Everything you need to know about depending on `@toolbox-web/grid`: which **environments** it
runs in (browsers, frameworks, SSR), how it behaves under a strict Content Security Policy,
and — in the second half — the **commitments** that govern what breaks, when, and how you find
out.

The grid is **feature-complete**. There is no public roadmap, because it already does what it
set out to do — and "we plan to add X" is not something you should have to bet a production
dependency on. What you _should_ be able to bet on is this page. Every statement below is
checkable against the repository: the version and peer-range tables are generated at
docs-build time from the packages' `package.json` files, and the policies describe what the
project has actually done across v1, v2, and v3.

## Support at a glance

| | |
| --- | --- |
| **Current core version** | `@toolbox-web/grid` v3.8.0 |
| **Current adapter versions** | `grid-angular` v2.6.0 · `grid-react` v2.6.0 · `grid-vue` v2.6.0 |
| **Runtime dependencies (core)** | 0 |
| **License** | MIT |
| **Versioning** | Semantic Versioning, per package |
| **Release automation** | [release-please](https://github.com/googleapis/release-please) from Conventional Commits |
| **Distribution** | npm, `latest` dist-tag (prereleases go to `next`) |

## Framework Support

The adapters are thin wrappers around the same web component. Their supported framework
versions are whatever their published `peerDependencies` say — which is what npm, pnpm, and
bun actually enforce at install time:

| Package | Version | Framework peer | Requires |
| ------- | ------- | -------------- | -------- |
| `@toolbox-web/grid-angular` | 2.6.0 | `@angular/core` >=17.0.0 | `@toolbox-web/grid` ^3.0.0 |
| `@toolbox-web/grid-react` | 2.6.0 | `react` and `react-dom` >=18.0.0 | `@toolbox-web/grid` ^3.0.0 |
| `@toolbox-web/grid-vue` | 2.6.0 | `vue` >=3.3.0 | `@toolbox-web/grid` ^3.0.0 |

The ranges are **open-ended on the upper bound** (`>=`) on purpose: a new framework major does
not, by itself, invalidate the adapter. If one genuinely breaks it, that is a bug fix, not a
support drop.

### When a framework version is dropped

- A framework major is supported **for as long as its own vendor supports it**. The adapters
  do not drop a version the framework team still maintains.
- Raising a `peerDependencies` floor is a **breaking change**, so it only happens in an
  adapter **major** release, documented in that adapter's changelog.
- **You do not need an adapter.** `<tbw-grid>` is a standard custom element; if an adapter
  ever stops covering your framework version, the core package keeps working — you lose
  template-level ergonomics, not functionality. See
  [Framework Integration](https://toolboxjs.com/grid/framework-adapters.md) for what the adapters add on top.
- **Older adapter lines are not listed above.** This page always describes the current
  release. If you are pinned to an older framework major, the previous adapter major still
  has whatever range it published — ask npm rather than trusting a table:

  ```sh
  npm view @toolbox-web/grid-angular@2 peerDependencies
  ```

:::tip[Two grid versions on one page]
Micro-frontend setups can run two different grid majors side by side without a conflict — see
[Multi-version coexistence](https://toolboxjs.com/grid/guides/multi-version.md). That makes an incremental upgrade
possible without a big-bang migration across every team.
:::

## Browser Support

`@toolbox-web/grid` targets **modern evergreen browsers**. There is no transpilation to ES5
and no polyfill bundle — the grid ships the same standards-based code it was written in.

| Browser        | Minimum version | Released    |
| -------------- | --------------- | ----------- |
| Chrome / Edge  | **123**         | March 2024  |
| Firefox        | **121**         | Dec 2023    |
| Safari (macOS / iOS) | **17.5**  | May 2024    |

Older versions may work for basic rendering but are **not tested and not supported**. Raising
this baseline is treated as a **breaking change** and lands only in a major.

### What sets the floor

The minimums above are driven by the platform features the grid depends on. Each row is a
hard requirement unless marked as progressively enhanced.

| Platform feature | Used for | Chrome/Edge | Firefox | Safari |
| ---------------- | -------- | ----------- | ------- | ------ |
| [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark) | Every themeable color token resolves light/dark in one declaration | 123 | 120 | 17.5 |
| [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) | Row/cell state styling, print isolation | 105 | 121 | 15.4 |
| [CSS Nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting) | Authored style sheets | 120 | 117 | 17.2 |
| [`@layer`](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) | `tbw-base` → `tbw-plugins` → `tbw-theme` cascade order | 99 | 97 | 15.4 |
| [`adoptedStyleSheets`](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets) | Style injection that survives DOM rebuilds | 73 | 101 | 16.4 |
| [Custom Elements v1](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) | `<tbw-grid>` itself | 67 | 63 | 10.1 |
| `ResizeObserver` / `IntersectionObserver` | Virtualization, column fitting, responsive layout | 64 | 69 | 13.1 |
| [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) | Tooltips, dropdown tool panel — **progressively enhanced** | 114 | 125 | 17 |
| [CSS anchor positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning) | Tooltip placement — **progressively enhanced** | 125 | — | — |

:::note[Why no `light-dark()` fallback?]
The theme system defines each color once and lets the browser pick the light or dark value.
Emitting a `prefers-color-scheme` fallback for every token would roughly double the size of
the shipped CSS and reintroduce the flash-of-wrong-theme the token system exists to avoid.
:::

### Progressive enhancement

Two features degrade rather than break when the platform lacks support:

- **Tooltip and dropdown tool panel** — the [Tooltip plugin](https://toolboxjs.com/grid/plugins/tooltip.md) checks
  `HTMLElement.prototype.showPopover` before using the top layer, and checks
  `CSS.supports('anchor-name', '--x')` before using anchor positioning. Where either is
  missing it falls back to absolute positioning computed from bounding rects.
- **Print isolation** — the [Print plugin](https://toolboxjs.com/grid/plugins/print.md) uses `:has()` to hide
  everything outside the grid. This is a hard requirement; the plugin has no fallback.

### Testing matrix

The e2e suite runs against Chromium, Firefox, and WebKit via Playwright. Unit tests run in
[happy-dom](https://github.com/capricorn86/happy-dom), which is a DOM emulation and **not** a
browser — behaviour differences (layout, `getBoundingClientRect`, CSS cascade) are covered by
e2e, not unit tests.

- [Automated testing](https://toolboxjs.com/grid/guides/automated-testing.md): Selectors and helpers for testing the grid

---

## Security

The grid is a rendering engine for **your** data. It never fetches, never evaluates, and never
persists anything on its own — but the extension points it gives you can introduce
vulnerabilities if used carelessly.

Its strongest security property is structural: the core declares
**0 runtime dependencies**, and so do all three adapters. There
is no transitive dependency tree to audit, and no supply-chain surface beyond the package
itself.

### Reporting a vulnerability

| | |
| --- | --- |
| **Report privately** | [GitHub Security Advisories](https://github.com/OysteinAmundsen/toolbox/security/advisories/new) |
| **Report publicly** | [A new issue](https://github.com/OysteinAmundsen/toolbox/issues/new) with the `security` label |
| **Policy** | [`SECURITY.md`](https://github.com/OysteinAmundsen/toolbox/blob/main/SECURITY.md) |
| **Advisories published to** | The repository [Security tab](https://github.com/OysteinAmundsen/toolbox/security/advisories) and, from there, the GitHub Advisory Database |
| **Supported for fixes** | The current major only — fixes ship as a new patch release |
| **Repository controls** | GitHub secret scanning and push protection are enabled |

:::caution[Response time, honestly]
This is a solo-maintained open-source project. There is no contractual response SLA: critical
vulnerabilities are prioritised, everything else is handled as time allows. `SECURITY.md` says
the same thing — this page does not upgrade that promise.
:::

### What the grid sanitizes for you

Every path where the grid turns a **string you returned** into markup runs through an internal
sanitizer before it reaches `innerHTML`. The sanitizer strips dangerous tags (`script`,
`iframe`, `object`, `embed`, `form`, `style`, `link`, `meta`, `base`, and friends), all `on*`
event-handler attributes, and `javascript:` / `vbscript:` / `data:` / `blob:` URLs from
`href`, `src`, `srcdoc`, `formaction`, `poster`, and `srcset`.

This covers string-returning cell renderers, group-header renderers, `gridConfig.icons.*`
(icon strings are usually inline SVG), and light-DOM tool panel fallback content.

### What you must sanitize yourself

Renderers that receive a DOM element and write to it directly **bypass** that sanitizer. Writing
user-controlled data to `innerHTML` yourself is the single most likely way to introduce XSS.

```ts
// ❌ XSS: `row.notes` may contain <img src=x onerror=...>
renderer: (cell, { row }) => {
  cell.innerHTML = row.notes;
}

// ✅ Text is escaped by the DOM
renderer: (cell, { row }) => {
  cell.textContent = row.notes;
}

// ✅ Structured markup, still safe
renderer: (cell, { row }) => {
  const badge = document.createElement('span');
  badge.className = 'badge';
  badge.textContent = row.status;
  cell.replaceChildren(badge);
}
```

If you genuinely need rich HTML from an untrusted source, sanitize it with a hardened library
such as [DOMPurify](https://github.com/cure53/DOMPurify) — do not hand-roll an escaper. The
grid's built-in sanitizer is **defense in depth for its own render paths**, not a general-purpose
sanitization service; it is not exported.

- [Renderer security](https://toolboxjs.com/grid/core.md#renderer-security-avoid-innerhtml): The full rule in the core configuration guide

### Content Security Policy

The grid injects its styles through
[`document.adoptedStyleSheets`](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets),
built from `CSSStyleSheet` objects. Constructable style sheets are **not** governed by
`style-src`, so a strict policy works without `'unsafe-inline'`:

```
Content-Security-Policy: default-src 'self'; style-src 'self'; script-src 'self';
```

Notes:

- **No `'unsafe-eval'` is required.** The grid never calls `eval`, `new Function`, or
  `setTimeout` with a string. `{{ }}` template expressions are evaluated by a purpose-built
  recursive-descent parser over an allowlisted grammar, not by dynamic code generation.
- **`img-src`** must allow whatever your renderers load. The grid's own icons are inline SVG
  markup, not external images.
- **Inline `style` attributes** are set imperatively (`element.style.width = …`) for column
  widths and virtualization offsets. These are DOM property writes, not CSS text, so they are
  not blocked by `style-src`. You do **not** need `style-src-attr 'unsafe-inline'`.
- If styles silently fail to apply, see
  [Styles not applying (CSP)](https://toolboxjs.com/grid/guides/troubleshooting.md#styles-not-applying-csp).

### Trusted Types

`require-trusted-types-for 'script'` is **not** supported out of the box. The grid's sanitizer
parses markup by assigning to a detached `<template>` element's `innerHTML`, which is a Trusted
Types sink. If you enforce Trusted Types, install a default policy that runs your own sanitizer:

```ts
if (window.trustedTypes?.createPolicy) {
  window.trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
  });
}
```

:::note[What the built-in sanitizer already does]
The policy above is required only because the parse step itself is a Trusted Types sink — it is
not a sign that the grid hands raw markup to the DOM. Every string returned by a `renderer`,
`headerRenderer` or template is first passed through the grid's sanitizer, which:

- drops non-allow-listed elements (`<script>`, `<iframe>`, `<object>`, `<embed>`, `<link>`,
  `<meta>`, `<base>`, `<form>`);
- strips every `on*` event-handler attribute;
- strips the `is=` attribute, so markup cannot upgrade a plain tag into a registered
  customized built-in and smuggle behaviour past the tag allow-list;
- rejects `javascript:` / `vbscript:` / `data:` URLs in `href`, `src`, `action` and friends;
- rejects `expression()`, `javascript:` and `behavior:` inside inline `style`;
- HTML-escapes every `{{ }}` interpolation in template strings, so row data can never
  contribute markup or break out of an attribute.

Your default policy therefore runs **in addition to** those guarantees, not instead of them.
:::

### Handling untrusted data

- **Validate before assigning to `grid.rows`.** The grid trusts the shape you give it. Rows
  with duplicate or missing IDs cause incorrect selection and edit tracking, not an error.
- **Clipboard and paste.** The [Clipboard plugin](https://toolboxjs.com/grid/plugins/clipboard.md) writes values into
  your row objects. Use the `onPaste` column hook to validate and coerce incoming values before
  they land in your model.
- **Export.** The [Export plugin](https://toolboxjs.com/grid/plugins/export.md) writes CSV and Excel XML. Values that
  start with `=`, `+`, `-`, `@`, a tab or a carriage return are prefixed with `'` so a
  spreadsheet treats them as text — this guards against
  [CSV injection](https://owasp.org/www-community/attacks/CSV_Injection) and is **on by
  default**. Only turn it off (`escapeFormulas: false`) when the exported data is fully
  trusted and the leading apostrophe is unacceptable.

---

## Server-Side Rendering

`<tbw-grid>` is a custom element. Custom elements are a **browser** API — there is no DOM to
upgrade on the server, so the grid renders no markup during SSR.

:::caution[Do not import the grid on the server]
Importing `@toolbox-web/grid` registers the custom element at module-evaluation time, which
touches `customElements`. That global does not exist in Node, so a top-level server import
throws. Always load the grid behind a client-only boundary — the recipes below show how for
each framework.
:::

Beyond that one rule, there is nothing to reconcile:

- **Hydration is automatic.** On the client the element upgrades and renders as soon as it is
  connected. There is no hydration mismatch, because the server emitted no grid content.
- **Feature side-effect imports follow the same rule.** `@toolbox-web/grid/features/selection`
  only writes a factory into a module-scoped map, but it pulls in the core module — so keep it
  inside the same client-only boundary as the grid itself.

:::caution[`ssr` prop removed in v3]
Both the React and Vue adapters used to accept an `ssr` prop (and export an `SSRProps` type).
It was a no-op and was removed in v3 — see the
[v3 migration guide](https://toolboxjs.com/grid/guides/migration-v3.md#3-ssrprops--ssr-prop-removed).
:::

### Framework recipes

| Framework | Recommendation |
| --------- | -------------- |
| **Next.js (App Router)** | Mark the component `'use client'` **and** load it via `next/dynamic` with `{ ssr: false }` — `'use client'` alone still pre-renders on the server. |
| **Remix / React Router** | Render inside a `ClientOnly` boundary, or gate on a `useEffect`-set `mounted` flag. |
| **Nuxt** | Wrap the grid in `<ClientOnly>`. |
| **Astro** | Use `client:only="react"` / `"vue"` (or `client:only` for the vanilla element). |
| **Angular SSR** | Guard the import behind `isPlatformBrowser(...)`, or defer it with `@defer (on viewport)`. |

Reserve the grid's height in your server-rendered markup (e.g. a wrapper with a fixed
`height`) so hydration doesn't cause layout shift.

---

## Versioning & Deprecation

Semantic Versioning is applied **per package**. `@toolbox-web/grid` and each adapter are
independent release-please components, so a grid major does not force an adapter major unless
the adapter's own API changes (its peer range widening usually does).

### What the JSDoc tags guarantee

| Tag | Meaning |
| --- | ------- |
| `@since <version>` | The symbol has existed, with this behaviour, since that version. Rendered as a version pill in the [API reference](https://toolboxjs.com/grid/api-reference.md). |
| `@deprecated` | Still works, still tested, still shipped. Scheduled for removal in a future major, with the replacement named in the same comment. |
| `@internal` | Exported for advanced plugin authors, **not** covered by SemVer. May change in a minor. |

### The removal window

**A public symbol is deprecated for at least one full major before it is removed.** That is
the practice v1 → v2 → v3 actually followed: symbols marked `@deprecated` during the v1 line
were removed in v2, and the v2 deprecations were removed in v3.

Removals are listed in the migration guide for that major, with the replacement for each one.
The v3 guide is the precedent to look at:

- [Migrating to v3](https://toolboxjs.com/grid/guides/migration-v3.md): Every removal in v3, why it happened, and the exact replacement

At the time of writing there are **no `@deprecated` symbols** anywhere in the shipped source of
the grid or the three adapters — v3 cleared the backlog. Anything newly deprecated must name
its removal major in the same comment.

### Maintenance branches

When work on the next major starts, the previous major gets a long-lived `<major>.x` branch
(`2.x` today) which keeps its own release stream, so an important fix can be cherry-picked
back. There is **no formal LTS window**: the previous major is maintained on a best-effort
basis, and staying on the current major is the supported path.

---

## Plugin API Compatibility

Every one of the 24 shipped plugins is built on the **same public API you
get**. There is no privileged internal channel, no "enterprise" hook set, and no plugin that
reaches into something you cannot reach into. If a first-party plugin can do it, a third-party
plugin can do it — which is also why the plugin API cannot quietly rot: breaking it would break
the grid's own feature set first.

### What is covered by SemVer

Everything exported from the package root `@toolbox-web/grid` and from
`@toolbox-web/grid/plugins/*`:

- `BaseGridPlugin` and its lifecycle (`init`, `afterRender`, `destroy`, …)
- Hook payload types — `CellClickEvent`, `RowClickEvent`, `HeaderClickEvent`, `ScrollEvent`,
  `CellCoords`, `HookName`
- Render-context types — `AfterCellRenderContext`, `AfterRowRenderContext`,
  `CollectHeaderRowsContext`, `HeaderRowCell`, `HeaderRowContribution`
- The inter-plugin contract — `PluginManifest`, `PluginDependency`, `PluginQuery`,
  `QueryDefinition`, `EventDefinition`
- DOM contracts — `GridClasses`, `GridSelectors`, `GridDataAttrs`, `GridCSSVars`

### What is not

Types documented as `@internal` — `InternalGrid`, `ColumnInternal`, `VirtualState`,
`CellContext`, `ResizeController`, and the underscore-prefixed members they expose — are
exported so power users are not forced into `as any`, but they describe implementation details
and **can change in a minor release**. A plugin built only on the list above will not be broken
by a minor.

### What a major obliges

When the grid takes a major, third-party plugin authors get: the removals enumerated in the
migration guide, a deprecation cycle that preceded them, and a `peerDependencies` range to
widen. Plugins that stayed on the public API surface typically need only the range bump.

- [Writing a custom plugin](https://toolboxjs.com/grid/plugin-development/custom-plugins.md): The plugin lifecycle, hooks, manifest, and inter-plugin queries

---

## Release Cadence

Releases are automated, not scheduled: every merge to `main` that carries a `feat`/`fix`/`perf`
commit updates a release-please pull request, and merging that PR tags, publishes to npm, and
deploys the docs. There is no release train to wait for.

| | |
| --- | --- |
| **Patch** (`fix:`, `perf:`) | Bug fixes and optimisations. Always safe to take. |
| **Minor** (`feat:`, `enhance:`) | Additive API. Existing code keeps compiling. |
| **Major** (`!` / `BREAKING CHANGE:`) | Removals and behaviour changes, with a migration guide. |
| **Prereleases** | Published under the `next` dist-tag, never `latest`. |

For reference, the actual history: **over 120 stable `@toolbox-web/grid` releases** since
2026-01-21, with majors on 2026-01-21 (v1), 2026-04-16 (v2), and 2026-07-14 (v3) — roughly one
major per quarter, each with a written migration guide.

Every release runs the same gate: unit tests, lint, build, bundle-size budget, Playwright e2e
across three browser engines, and a performance benchmark suite. A regression in any of them
blocks the release.

- [Changelog](https://toolboxjs.com/grid/changelog.md): Every release, generated from the commit history

### What this page does not promise

Being explicit about the gaps is the point of the exercise:

- **No contractual SLA.** Solo-maintained MIT software. Response times are best-effort.
- **No LTS.** Only the current major receives fixes as a matter of policy.
- **No paid support tier.** If continuity matters to you, the mitigations are the MIT licence,
  the zero-dependency footprint, and the fact that the whole thing is a standard custom element
  you can vendor.
- **No feature roadmap.** New features arrive when someone needs them and opens an issue.

:::note[Verified as of]
The support and stability sections reflect `@toolbox-web/grid` v3.8.0 with
the three adapters at v2.6.0. The version and peer-range tables
are read from the packages at docs-build time, so they cannot drift — and where one cell stands
for several packages (`react` with `react-dom`, the core range across all three adapters), the
docs build **fails** if those ranges ever stop matching.
:::

---

## See Also

  - [Accessibility](https://toolboxjs.com/grid/guides/accessibility.md): ARIA patterns, keyboard navigation, screen reader support
  - [Internationalization](https://toolboxjs.com/grid/guides/i18n.md): Translating announcements, plugin UI strings, and cell values
  - [Theming](https://toolboxjs.com/grid/guides/theming.md): Design tokens, light/dark, custom themes
  - [Troubleshooting](https://toolboxjs.com/grid/guides/troubleshooting.md): Common issues and their fixes
  - [Performance](https://toolboxjs.com/grid/guides/performance.md): Profiling, virtualization tuning, and bundle optimization
  - [Compared to other grids](https://toolboxjs.com/grid/comparison.md): How the trade-offs stack up against AG Grid, Tabulator, SlickGrid
