Multi-version coexistence
This guide is framework-agnostic. Multi-version coexistence is a property of the underlying web component (<tbw-grid> and its custom-element registry), not of any adapter — it applies equally to vanilla JavaScript, Module Federation, Native Federation, iframe-less micro-frontend shells, and any setup where two copies of @toolbox-web/grid end up on the same page. You don’t need a framework adapter, and you don’t need to install anything extra.
When two micro-frontends on the same page each ship their own copy of
@toolbox-web/grid (for example, a host app on v2.14 while a widget is
still on v2.11), the browser’s custom-elements registry is shared and
first-wins: a second customElements.define('tbw-grid', …) call throws.
Starting with v2.14, the grid handles this for you automatically — the
second bundle to load registers itself under a version-suffixed tag and the
two implementations coexist.
How it works
Section titled “How it works”Each grid bundle exposes the version it was built with on the class:
import { DataGridElement } from '@toolbox-web/grid';
DataGridElement.version; // e.g. '2.14.0' — set at build timeDataGridElement.tagName; // 'tbw-grid' — the canonical tagDataGridElement.activeTag; // 'tbw-grid' OR 'tbw-grid-v<sanitised-version>'At module load the bundle calls customElements.define() using the
following rule:
- No existing registration → register under
tbw-grid. The bundle owns the bare tag.activeTag === 'tbw-grid'. - An existing registration at the SAME version → don’t re-register;
reuse the existing class.
activeTag === 'tbw-grid'. - An existing registration at a DIFFERENT version → register under
tbw-grid-v<sanitised-version>(e.g.tbw-grid-v2-14-0).activeTagreflects the suffixed tag.
The sanitiser replaces every non-alphanumeric character with - and
lower-cases the result, so semver pre-release / build-metadata strings like
2.14.0-beta.1+build.42 produce tbw-grid-v2-14-0-beta-1-build-42.
In the single-version case (99% of apps), nothing changes: the tag is
<tbw-grid> and existing markup, styles, and tooling continue to work
unmodified.
The data-tbw-grid selector contract
Section titled “The data-tbw-grid selector contract”Because the tag name is no longer guaranteed to be tbw-grid literally,
every grid sets a stable attribute on its host element when it connects:
<tbw-grid data-tbw-grid="">…</tbw-grid><tbw-grid-v2-14-0 data-tbw-grid="">…</tbw-grid-v2-14-0>Always use [data-tbw-grid] (not the tag name) when you need to target
“any grid” in CSS, querySelector, or closest(). The framework adapters
(@toolbox-web/grid-react, @toolbox-web/grid-vue,
@toolbox-web/grid-angular) already do this internally — your application
code only needs to follow the same rule for hand-written selectors:
/* ✅ Works for both bare and suffixed grids */[data-tbw-grid] { font-size: 14px; }
/* ⚠️ Only matches the first-loaded bundle's bare tag */tbw-grid { font-size: 14px; }The bundled themes (@toolbox-web/grid/themes/dg-theme-*.css) already use
[data-tbw-grid] for their root selectors, so they apply correctly to every
loaded version. If you author a custom theme, follow the same pattern.
Adapter usage
Section titled “Adapter usage”The React and Vue adapters resolve activeTag at render time, so they
always emit the correct tag for the bundle they imported:
// React — renders <tbw-grid> or <tbw-grid-v…> depending on which bundle// supplied DataGridElement.import { DataGrid } from '@toolbox-web/grid-react';<!-- Vue — same behaviour via <component :is="…"> internally. --><TbwGrid :rows="rows" :columns="columns" />The Angular adapter’s Grid directive matches both the bare tbw-grid tag
and the stable [data-tbw-grid] attribute, and its shell-content
directives (tbw-grid-header-content, tbw-grid-toolbar-content) locate their
host via closest('[data-tbw-grid]'). In single-version setups embed
<tbw-grid> literally as before. For multi-version Angular usage, render the
active tag for the bundle you imported and add data-tbw-grid so the directive
attaches (Angular matches selectors at compile time, so the runtime tag can’t be
matched by name alone):
<!-- activeTag for this bundle is e.g. 'tbw-grid-v2-15-0' --><tbw-grid-v2-15-0 data-tbw-grid [rows]="rows" [gridConfig]="config"> <tbw-grid-header-content [order]="0"> <ng-template let-grid>…</ng-template> </tbw-grid-header-content></tbw-grid-v2-15-0>Per-bundle styles
Section titled “Per-bundle styles”The internal style injector scopes its <style id="tbw-grid-styles-…">
element to the active tag, and rewrites bare tbw-grid selectors in the
injected CSS to the active tag. Each bundle ships its own stylesheet
without overwriting the other.
Imperative helpers
Section titled “Imperative helpers”The createGrid() and queryGrid() helpers from @toolbox-web/grid resolve
DataGridElement.activeTag internally, so they operate on the tag owned by the
bundle that exported them:
import { createGrid, queryGrid } from '@toolbox-web/grid';
// Creates <tbw-grid> or <tbw-grid-v…> depending on which bundle you imported.const grid = createGrid({ columns: [{ field: 'name' }] });
// `awaitUpgrade` waits for the active tag's upgrade, not the bare tag.const found = await queryGrid('[data-tbw-grid]', true);When you query with a hand-written selector, prefer [data-tbw-grid] over
tbw-grid so it matches a suffixed grid too.
The globalThis.DataGridElement alias
Section titled “The globalThis.DataGridElement alias”For convenience the grid exposes its class on globalThis.DataGridElement,
but this alias is first-wins and points at the first-loaded bundle’s
class. Multi-version-aware code that needs a specific version’s class must
import it directly (import { DataGridElement } from '@toolbox-web/grid')
or look it up via customElements.get(activeTag). Don’t rely on the global
when multiple bundles may be present.