Skip to content

Shell Plugin

The Shell plugin wraps the grid with an optional header bar (title + toolbar) and a collapsible tool panel sidebar. Features like visibility, filtering, and pivot register their tool panels into this shell.

import '@toolbox-web/grid/features/shell';

The feature import registers the ShellPlugin and augments features with a typed shell option. Shell configuration types (ShellConfig, ToolPanelDefinition, …) are exported from @toolbox-web/grid/plugins/shell.

Enable the shell by setting features: { shell } with a header.title. Features like visibility automatically register a tool panel when the shell is active.

import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/shell';
import '@toolbox-web/grid/features/visibility';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
features: {
shell: { header: { title: 'Employee Data' } },
visibility: true,
},
};
Show title Show title in shell header
Show header content Show custom header content (row count)
Show toolbar button Show custom toolbar button
Visibility plugin Include VisibilityPlugin (column chooser)
Custom tool panel Show custom tool panel via API
Panel position Tool panel position
Panel mode Overlay floats over the grid; push reflows the grid; dropdown anchors a popover to the toggle

Configure the shell declaratively using <tbw-grid-header> and <tbw-grid-header-content> elements. Still import '@toolbox-web/grid/features/shell' so the plugin is present:

<tbw-grid>
<tbw-grid-header title="Employee Directory">
<tbw-grid-header-content>
<span id="row-count">0 employees</span>
</tbw-grid-header-content>
</tbw-grid-header>
</tbw-grid>

Shell configured via <tbw-grid-header> light DOM elements — no JavaScript shell config needed.

20 employees

Register multiple tool panels — each gets a tab in the sidebar. Panels can be registered via configuration, the framework wrapper components, or the plugin’s runtime registerToolPanel() API. Each panel requires a title.

import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');
shell?.registerToolPanel({
id: 'filter-panel',
title: 'Filters',
icon: '🔍',
render(container) {
container.innerHTML = '<p>Filter controls here</p>';
},
});

Multiple tool panels (Columns, Filter, Settings). Click toolbar icons to toggle each.

Add custom buttons to the shell toolbar using framework wrapper components or light-DOM <tbw-grid-tool-buttons>:

<tbw-grid>
<tbw-grid-tool-buttons>
<button onclick="exportData()">📥 Export</button>
<button onclick="printGrid()">🖨️ Print</button>
</tbw-grid-tool-buttons>
</tbw-grid>

Custom toolbar buttons via light DOM. The grid provides the container — you provide the HTML.

All options live under features.shell and are typed by ShellConfig (from @toolbox-web/grid/plugins/shell):

OptionTypeDefaultDescription
header.titlestringTitle text in the header bar
header.visiblebooleantrueRender the header bar. Set false to suppress the entire .tbw-shell-header and own all chrome yourself
header.toolPanelTogglebooleantrueRender the built-in panel toggle button + separator
toolPanel.position'left' | 'right''right'Sidebar position
toolPanel.widthnumber280Sidebar width in pixels
toolPanel.defaultOpenstringAccordion section to auto-expand on first open. Deprecated: in v2.x this also opens the sidebar; in v3.0.0 it will only pre-select the section (see #259). Combine with initialState: 'open' for forward-compatible behavior.
toolPanel.initialState'open' | 'closed''closed'Whether the sidebar starts open on grid load. Overrides the legacy defaultOpen open-on-load behavior when set.
toolPanel.lockedbooleanfalseLock the sidebar open. Implies initialState: 'open', makes closeToolPanel() a no-op, and hides the built-in toggle button.
toolPanel.persistStatebooleanfalseRemember open/closed state
toolPanel.closeOnClickOutsidebooleanfalseClose on outside click (ignored in mode: 'push' or when locked: true; 'dropdown' always light-dismisses)
toolPanel.mode'overlay' | 'push' | 'dropdown''overlay''overlay' floats over the grid; 'push' reflows the grid sideways so all cells stay visible; 'dropdown' renders the whole sidebar as an anchored popover

The tool panel supports three layout modes — pick whichever fits the viewport you’re targeting.

  • 'overlay' (default): the panel is positioned over the grid content. Opening it does not change the grid’s available width. Best for narrow viewports where shrinking the grid would leave too little room for the data.
  • 'push': the panel is laid out as a sibling of the grid content in a flex row. Opening it shrinks the grid’s available width; the grid’s ResizeObserver re-runs column virtualization, so cells that an overlay panel would hide remain reachable. Best for desktop layouts.
  • 'dropdown': the entire sidebar (the full accordion) renders as an anchored popover built on the native Popover API. It floats above all other content in the top layer, is dismissed on Escape or click-outside, and is positioned via CSS anchor positioning where supported (with a JS bounding-rect fallback). The anchor is resolved by priority: an explicit anchor passed to openToolPanel() → the built-in toggle button → the grid corner. Best for compact toolbars and bring-your-own trigger buttons (e.g. a “columns” button in a custom column header).
import '@toolbox-web/grid/features/shell';
grid.gridConfig = {
features: {
shell: {
toolPanel: {
position: 'right',
mode: 'push', // grid reflows when the panel opens/closes
},
},
},
};

In 'push' mode the user-facing resize handle is hidden (the panel’s width is part of the layout, so drag-to-resize would fight the flex container) and closeOnClickOutside is treated as a no-op (there is no overlap to dismiss against). Drive the width via the --tbw-tool-panel-width CSS variable instead.

Section titled “Dropdown mode + a custom column-header trigger”

'dropdown' mode shines when you want the column chooser to hang off your own button rather than the built-in toolbar toggle. Pass that button as the anchor when you open the panel, and the popover positions itself directly below it:

const shell = grid.getPluginByName('shell');
// Mark your trigger so the popover can re-find it after a structural re-render.
columnsButton.setAttribute('data-panel-toggle', '');
columnsButton.addEventListener('click', () => {
shell?.openToolPanel('columns', { anchor: columnsButton });
});

The demo below renders a utility-type column whose header hosts a custom button. Clicking it opens the full tool panel as a dropdown anchored to that button — no toolbar required.

mode: 'dropdown' renders the full tool panel as an anchored popover. Here it is opened from a custom button in the header of a utility column — a bring-your-own trigger anchored via openToolPanel('columns', { anchor }).

In 'dropdown' mode the resize handle is hidden, the popover is capped to the viewport (max-height: min(70vh, 480px)) with internal scrolling for tall accordions, and dismissal (Escape / click-outside) is always active regardless of closeOnClickOutside.

<tbw-grid> renders into the light DOM, so the shell’s internals are reachable from any outer stylesheet — no ::part() needed. Theme-level overrides applied to a top-level CSS file affect every grid in the application uniformly.

Stable selectors:

SelectorElement
tbw-grid .tbw-shell-headerHeader bar (title + content + toolbar)
tbw-grid .tbw-shell-titleTitle element
tbw-grid .tbw-shell-toolbarToolbar (flex container; custom content + toggle)
tbw-grid .tbw-toolbar-btnButtons inside the toolbar (incl. panel toggle)
tbw-grid [data-panel-toggle]The single panel toggle button
tbw-grid .tbw-toolbar-separatorSeparator between custom content and the toggle
tbw-grid .tbw-tool-panelTool-panel sidebar
tbw-grid .tbw-accordion-headerTool-panel section header (one per registered panel)

Recipe — move the panel toggle to the start of the toolbar (left in LTR):

The toolbar is a flex row, so reordering is purely a CSS concern — use order on both the toggle and its separator so they stay grouped together:

/* App-level theme — affects every <tbw-grid> uniformly */
tbw-grid [data-panel-toggle],
tbw-grid .tbw-toolbar-separator {
order: -1;
}

No grid configuration is required; this is the recommended way to standardize toolbar layout across an application’s grids.

If you need to replace the built-in toggle button entirely — for example, to use a design-system button instead of styling .tbw-toolbar-btn — set header.toolPanelToggle: false. The grid will not render the built-in button or the auto-inserted separator next to it; tool panels themselves remain fully functional and can be toggled from any element via the plugin’s toggleToolPanel(), opened directly on a specific section via openToolPanel(panelId), or driven section-by-section with toggleToolPanelSection(id).

import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');
const config = {
features: {
shell: {
header: {
title: 'Employees',
toolPanelToggle: false, // suppress built-in <button data-panel-toggle> + separator
toolbarContents: [
{
id: 'my-buttons',
render: (container) => {
const filters = document.createElement('my-button');
filters.textContent = 'Filters';
filters.addEventListener('click', () => shell?.openToolPanel('filters'));
const settings = document.createElement('my-button');
settings.textContent = 'Settings';
settings.addEventListener('click', () => shell?.openToolPanel('settings'));
container.append(filters, settings);
},
},
],
},
},
},
};

Passing a panelId to openToolPanel() jumps straight to that accordion section (one click, no double-tap). It takes precedence over toolPanel.defaultOpen; if the ID isn’t registered, the call falls back to default behavior and emits a TBW072 warning.

toolPanelToggle: false removes the built-in toggle button but keeps the header bar (with its title and any toolbar contents). To suppress the entire .tbw-shell-header bar — for example when you want to open tool panels from a custom column header icon and own all chrome yourself — set header.visible: false.

import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');
const config = {
columns: [
// …
{
field: '__actions',
width: 56,
utility: true, // excluded from print, export, reorder, visibility, etc.
resizable: false,
sortable: false,
headerRenderer: () => {
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = '';
btn.setAttribute('aria-label', 'Open tool panel');
btn.addEventListener('click', () => shell?.toggleToolPanel());
return btn;
},
},
],
features: {
shell: {
header: { visible: false },
toolPanel: {
closeOnClickOutside: true, // optional — see below
},
},
},
};

When header.visible: false, tool panels remain fully functional and are opened with the plugin’s toggleToolPanel() / openToolPanel(panelId) from any element you like. Because there is no header toggle to close the panel, the grid provides three additional dismissal affordances:

  • Close button — a small button (exposed as the tool-panel-close CSS part) is rendered to dismiss the panel. With a single panel it sits inline on the first accordion header row; with multiple panels it gets its own row at the top of the panel (exposed as the tool-panel-header CSS part). It is omitted when toolPanel.locked is true (a locked panel cannot be closed) and in dropdown mode, where the popover already light-dismisses on Esc / click-outside.
  • Escape key — pressing Esc closes an open overlay panel. It yields to more specific handlers (e.g. an active cell editor that has already handled the key), and is a no-op for push-mode panels, which are persistent sidebars.
  • Click-outside — set toolPanel.closeOnClickOutside: true to dismiss an open overlay panel when clicking anywhere in the window outside the panel. This is opt-in and, like Esc, is a no-op in push mode.

header.visible: false keeps the shell active (the panel overlay still mounts); it only suppresses the header bar element. This is the supported alternative to hiding .tbw-shell-header with CSS.

The shell used to be part of grid core: auto-registered with no import, driven by grid.register* / openToolPanel element delegates, with its config types exported from the package root. Those surfaces are deprecated and will be removed in v3.0.0. The shell configuration object itself is augmented onto gridConfig by the plugin and is not deprecated. Migrate as follows:

Deprecated (v2.x, removed at v3)Use instead
Implicit shell (no import)import '@toolbox-web/grid/features/shell'
grid.registerToolPanel(panel)grid.getPluginByName('shell')?.registerToolPanel(panel)
grid.unregisterToolPanel(id)grid.getPluginByName('shell')?.unregisterToolPanel(id)
grid.registerHeaderContent(c)grid.getPluginByName('shell')?.registerHeaderContent(c)
grid.registerToolbarContent(c)grid.getPluginByName('shell')?.registerToolbarContent(c)
grid.openToolPanel(id) / closeToolPanel() / toggleToolPanel()grid.getPluginByName('shell')?.openToolPanel(id) (same method names)
Shell types from @toolbox-web/gridShell types from @toolbox-web/grid/plugins/shell

Notes:

  • In v2.x the shell is still auto-registered, so unmigrated code keeps working — the imperative grid.* shell delegates emit a one-time TBW076 deprecation warning. The top-level config.shell field is a plugin augmentation (not deprecated) and refreshShellHeader() is merged/handled silently.
  • For advanced/explicit control you can register the plugin directly: plugins: [new ShellPlugin(shellConfig)] (from @toolbox-web/grid/plugins/shell).
  • Adapters auto-register the ShellPlugin in v2.x, so React/Vue/Angular users need no change; this auto-registration is removed at v3.0.0 (opt-in). The forward-compatible opt-in is the adapter-specific side-effect import import '@toolbox-web/grid-{react,vue,angular}/features/shell'.
  • From v3.0.0 the shell is opt-in: shell usage without the feature/plugin throws a clear error with a migration link.

The shell is unusual among plugins: most plugins enrich the grid (tagging rows, decorating cells), but the shell wraps it — it consumes the freshly built grid DOM and renders its own chrome (header bar + tool-panel sidebar) around it. It does this from the afterStructuralRender() lifecycle hook, relocating the existing .tbw-grid-content node into the shell wrapper (preserving the subtree, its listeners, and the grid’s cached refs) rather than re-creating it.

If you want to build another grid-wrapping plugin, this pattern is documented as a reusable recipe in Plugin Architecture → Wrapping plugins.

  • Filtering — Registers a filter tool panel into the shell
  • Pivot — Registers a pivot configuration tool panel
  • Core Features — Columns, renderers, formatters, events, and methods
  • Plugins Overview — Plugin compatibility and combinations
AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://toolboxjs.com/llms-full.txt