Platform & Support
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
Section titled “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 from Conventional Commits |
| Distribution | npm, latest dist-tag (prereleases go to next) |
Framework Support
Section titled “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
Section titled “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
peerDependenciesfloor 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 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:
Terminal window npm view @toolbox-web/grid-angular@2 peerDependencies
Browser Support
Section titled “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
Section titled “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() | Every themeable color token resolves light/dark in one declaration | 123 | 120 | 17.5 |
:has() | Row/cell state styling, print isolation | 105 | 121 | 15.4 |
| CSS Nesting | Authored style sheets | 120 | 117 | 17.2 |
@layer | tbw-base → tbw-plugins → tbw-theme cascade order | 99 | 97 | 15.4 |
adoptedStyleSheets | Style injection that survives DOM rebuilds | 73 | 101 | 16.4 |
| Custom Elements v1 | <tbw-grid> itself | 67 | 63 | 10.1 |
ResizeObserver / IntersectionObserver | Virtualization, column fitting, responsive layout | 64 | 69 | 13.1 |
| Popover API | Tooltips, dropdown tool panel — progressively enhanced | 114 | 125 | 17 |
| CSS anchor positioning | Tooltip placement — progressively enhanced | 125 | — | — |
Progressive enhancement
Section titled “Progressive enhancement”Two features degrade rather than break when the platform lacks support:
- Tooltip and dropdown tool panel — the Tooltip plugin checks
HTMLElement.prototype.showPopoverbefore using the top layer, and checksCSS.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 uses
:has()to hide everything outside the grid. This is a hard requirement; the plugin has no fallback.
Testing matrix
Section titled “Testing matrix”The e2e suite runs against Chromium, Firefox, and WebKit via Playwright. Unit tests run in
happy-dom, which is a DOM emulation and not a
browser — behaviour differences (layout, getBoundingClientRect, CSS cascade) are covered by
e2e, not unit tests.
Security
Section titled “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
Section titled “Reporting a vulnerability”| Report privately | GitHub Security Advisories |
| Report publicly | A new issue with the security label |
| Policy | SECURITY.md |
| Advisories published to | The repository Security tab 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 |
What the grid sanitizes for you
Section titled “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
Section titled “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.
// ❌ XSS: `row.notes` may contain <img src=x onerror=...>renderer: (cell, { row }) => { cell.innerHTML = row.notes;}
// ✅ Text is escaped by the DOMrenderer: (cell, { row }) => { cell.textContent = row.notes;}
// ✅ Structured markup, still saferenderer: (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 — 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.
Content Security Policy
Section titled “Content Security Policy”The grid injects its styles through
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 callseval,new Function, orsetTimeoutwith a string.{{ }}template expressions are evaluated by a purpose-built recursive-descent parser over an allowlisted grammar, not by dynamic code generation. img-srcmust allow whatever your renderers load. The grid’s own icons are inline SVG markup, not external images.- Inline
styleattributes 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 bystyle-src. You do not needstyle-src-attr 'unsafe-inline'. - If styles silently fail to apply, see Styles not applying (CSP).
Trusted Types
Section titled “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:
if (window.trustedTypes?.createPolicy) { window.trustedTypes.createPolicy('default', { createHTML: (input) => DOMPurify.sanitize(input), });}Handling untrusted data
Section titled “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 writes values into
your row objects. Use the
onPastecolumn hook to validate and coerce incoming values before they land in your model. - Export. The Export plugin 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 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
Section titled “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.
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/selectiononly 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.
Framework recipes
Section titled “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
Section titled “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
Section titled “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. |
@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
Section titled “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:
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
Section titled “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
Section titled “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
Section titled “What is covered by SemVer”Everything exported from the package root @toolbox-web/grid and from
@toolbox-web/grid/plugins/*:
BaseGridPluginand 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
Section titled “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
Section titled “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.
Release Cadence
Section titled “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.
What this page does not promise
Section titled “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.