Overview
@deck.gl-community/panels builds inspectors, settings, editors, and application chrome
around a visualization. Use it with any canvas or DOM renderer; deck.gl is optional.
Build UI in three steps: define content panels, compose them, then choose a container and mount it with PanelManager. Application data stays in your application. Panel props describe what to show; callbacks report user changes.
yarn add @deck.gl-community/panels
The examples below use TypeScript and an application-owned DOM host with a definite size.
- Composable: built on
PanelComponent,Panel, and panel containers. - Framework-independent: owns Preact-rendered UI without requiring a React application.
- deck.gl integration: can be used with deck.gl's widget system, through
PanelWidgetadapters from@deck.gl-community/widgets.
Use this package when you want reusable panel composition, standalone mounting
through PanelManager, standalone panel theming, and application-managed UI
state such as toast notifications.
In-content panels
Start with panels that organize the UI inside a content area. A Panel defines a titled
piece of content; it does not decide where it sits relative to a canvas.
Leaf panels provide individual content units such as Markdown, settings, a table, or an editor. Composite panels arrange other panels into columns, tabs, accordions, or split views. Build up these content definitions first, then choose how to mount them.
import {MarkdownPanel, ColumnPanel, TabbedPanel} from '@deck.gl-community/panels';
const summary = new MarkdownPanel({
id: 'summary', title: 'Summary', markdown: '**Scene:** City center'
});
const selection = new MarkdownPanel({
id: 'selection', title: 'Selection', markdown: 'No object selected.'
});
const details = new ColumnPanel({
id: 'details', title: 'Details', panels: [summary, selection]
});
const help = new MarkdownPanel({
id: 'help', title: 'Help', markdown: 'Select an object to inspect its properties.'
});
const inspectorContent = new TabbedPanel({
id: 'inspector-content', title: 'Inspector', panels: [details, help]
});
inspectorContent now describes reusable content with tabs and a column of subpanels. Use
CustomPanel for imperative application DOM or extend Panel for Preact-rendered content.
The application continues to own its data and behavior.
Embed content in your own UI
If your application already has a sidebar or inspector surface, mount the content there directly:
import {mountPanel} from '@deck.gl-community/panels';
const mounted = mountPanel(inspectorContent, inspectorElement, {showTitle: true});
// Later, replace the content with another definition:
mounted.setPanel(nextInspectorContent);
// During application cleanup:
mounted.finalize();
Here inspectorElement is your application-owned DOM surface and nextInspectorContent
is a new panel definition. mountPanel adds no positioning
or outer chrome. Use a container when the panels package should own that shell.
Mount content in a container
A container supplies the outer shell. For example, display the composed content in a floating box:
import {PanelManager, BoxPanelContainer} from '@deck.gl-community/panels';
const manager = new PanelManager({parentElement, components: [new BoxPanelContainer({
id: 'inspector-box', title: 'Inspector', panel: inspectorContent,
placement: 'top-right'
})]});
Set headerPanel: quickControlsPanel on a box to keep essential controls available when its
body is collapsed. The header accepts any panel, remains mounted, and works with pinning too.
See persistent header controls.
PanelManager mounts panel-module components, reconciles them by id, and owns their DOM lifecycle.
Boxes, modals, and overlay sidebars can show the same composed content. This overlay-only setup
needs only an application-positioned parentElement; no canvas layout management is required.
Call manager.finalize() when the application is removed.
Update UI from application state
Every specialized panel renders from its current public props. Update a nested content panel directly without rebuilding its surrounding layout:
function showSelection(label: string) {
selection.setProps({markdown: label});
}
For applications that render a complete tree from state, pass fresh definitions through
manager.setProps({components}). Stable IDs and types preserve mounted instances and local
control state. A new ID or type starts a new lifecycle. The list describes the complete desired
UI: omit a component to remove it, or pass [] to clear it. Both update styles use the same
render-from-props implementation.
For controlled UI state, supply a value and its change callback. For local UI state, use
default… props. See state and updates
for tabs, expansion, visibility, and pinning.
Choose a container
| Container | Behavior | Needs contentElement? |
|---|---|---|
BoxPanelContainer | Floating card; optionally pinnable and collapsible | Only for pinning |
ModalPanelContainer | Dialog opened from a trigger | No |
SidebarPanelContainer | Overlay drawer, pinnable sidebar, or permanent side chrome | Only for pinning |
FullScreenPanelContainer | Expanded overlay | No |
HeaderPanelContainer / FooterPanelContainer | Space above / below the content | Yes |
SidebarPanelContainer overlays content when unpinned and resizes the content region when pinned.
Use pinnable to expose that choice to the user; use hidable for hide/restore controls.
Available panels and containers
Panels are titled content definitions. Leaf panels render one unit of content; composite panels arrange other panels; panel containers mount one panel inside box, modal, sidebar, or full-screen chrome.
- Core: Panel, Panel Themes
- Leaf panels: SourcePickerPanel, MetricsPanel, MarkdownPanel, SettingsPanel, KeyboardShortcutsPanel, URLParametersPanel, Arrow inspection panels, stats, docs links, binary data, and text editor panels
- Composite panels: AccordionPanel, ColumnPanel, SplitterPanel, TabbedPanel
- Panel containers: PanelContainer, BoxPanelContainer, ModalPanelContainer, SidebarPanelContainer, FullScreenPanelContainer, HeaderPanelContainer, FooterPanelContainer, optional pinning on BoxPanelContainer
Build chrome around a canvas
Chrome panels form the application UI around a canvas or other renderer. Reuse the content panels above inside permanent headers, footers, and side regions, or put them in a pinnable inspector. Docked chrome consumes layout space; floating containers overlay the canvas.
This mode adds an optional content host to the same PanelManager. The panels package remains
independent of deck.gl, and your application continues to own its renderer.
1. Create the application boundary and canvas host
The application creates two elements:
| Element | Application responsibility | Manager responsibility |
|---|---|---|
parentElement | Position and size the overall visualization area | Mount panel UI inside this boundary |
contentElement (optional) | Create it as a direct child and mount the renderer inside it | Arrange it alongside docked chrome and resize its available region |
The manager never wraps or changes the outer positioning of parentElement. It creates an
internal layout inside that boundary and moves the supplied contentElement into the center.
It does not collect or move unrelated application children.
import {PanelManager} from '@deck.gl-community/panels';
const parentElement = document.createElement('div');
Object.assign(parentElement.style, {
position: 'absolute', inset: '0', width: '100%', height: '100%'
});
const contentElement = document.createElement('div');
parentElement.append(contentElement);
document.body.append(parentElement);
const manager = new PanelManager({parentElement, contentElement});
// Initialize your canvas, map, or other renderer inside contentElement.
Give the parent a definite size using your application's layout. Let the renderer follow the
content host's changing dimensions through its automatic resizing support or a ResizeObserver.
Do not continue sizing the renderer against the larger outer boundary after adding chrome.
2. Define panel content and choose its shell
This example uses Markdown content; replace it with settings, composite panels, or CustomPanel
for application-specific DOM. Each mounted container needs a distinct id.
import {
MarkdownPanel, HeaderPanelContainer, FooterPanelContainer,
SidebarPanelContainer, BoxPanelContainer
} from '@deck.gl-community/panels';
const createTextPanel = (id: string, markdown: string) =>
new MarkdownPanel({id, title: id, markdown});
const inspector = new BoxPanelContainer({pinnable: true,
id: 'inspector', title: 'Inspector', side: 'right',
placement: 'top-left', offsetPx: 16, widthPx: 280,
panel: createTextPanel('selection', 'Select an object to inspect it.')
});
manager.setProps({components: [
new HeaderPanelContainer({
id: 'header', fitContent: true,
panel: createTextPanel('heading', '**Scene explorer**')
}),
new FooterPanelContainer({
id: 'status', heightPx: 36,
panel: createTextPanel('status-text', 'Ready')
}),
new SidebarPanelContainer({pinned: true, hidable: true, title: 'Tools',
id: 'tools', side: 'left', widthPx: 180, resizable: true,
panel: createTextPanel('tool-list', 'Layers · Selection · Settings')
}),
inspector
]});
Header and footer span the full width. Side chrome flanks the center between them. The inspector starts floating over the canvas; its pin button moves it to the right edge and makes the canvas region narrower. Unpinning returns it to its overlay anchor, preserving mounted content state. Collapsing its title releases the docked space and leaves a compact floating title bar. Expanding restores its pin state and resized dimensions.
Use widthPx for side thickness and heightPx for top/bottom thickness. fitContent: true
follows natural content size. With resizable: true, a divider supports pointer dragging and
arrow keys; manual resizing takes over from content fitting until you reset it through
setProps({fitContent: true}). Large panels are constrained and their content scrolls.
3. Let nested panels adapt to pinning
Use usePanelContainerState() inside Preact-rendered content to obtain the nearest chrome
container's {pinned, side, canPin} state. It updates on pin or edge changes, including inside
composite panels. It returns undefined outside chrome containers.
/** @jsxImportSource preact */
import {usePanelContainerState} from '@deck.gl-community/panels';
function InspectorContent() {
const layout = usePanelContainerState();
return <div style={{display: 'flex', flexDirection: layout?.pinned ? 'column' : 'row'}}>
<span>Selection</span><span>Properties</span>
</div>;
}
For application-controlled pinning, pass pinned and update it in response to
onPinnedChange. Otherwise, use defaultPinned for the initial state and let the container
handle its pin button.
4. Clean up with the application
// When the application is removed:
manager.finalize();
// Also dispose your renderer and disconnect any application-owned resize observer.
Finalization removes managed panel DOM and restores the content element's original position and affected inline styles. Keep the content element stable for the manager's lifetime; recreate the manager to use a different one.
Overlay-only applications
Omit contentElement to keep existing overlay-only usage:
const overlays = new PanelManager({parentElement});
Floating containers still work. Pinnable panels that are floating have a disabled pin button. Permanent header/footer/side chrome and pinnable containers configured as pinned stay hidden. Their configured state is preserved, and no pin-change callback fires because docking is unavailable.
Use standalone PanelManager for canvas chrome, including in deck.gl applications.
PanelWidget adapters remain useful for existing overlays but do not provide the content layout
contract needed for docking. The panels package itself does not import deck.gl.
Sidebars support hide/restore controls with hidable: true (the default). Use open and
onOpenChange to control visibility, or defaultOpen for initial local state. Closing a pinned
sidebar releases canvas space and leaves a floating restore button.
Try the complete example
The Canvas Chrome website example combines permanent chrome,
a pinnable inspector, a canvas that follows the remaining space, and adaptive nested content.
The source
exports mountCanvasChromeExample(container) and returns a cleanup function.
Components
Components are panel-owned mountable UI that are not necessarily panels.
PanelComponent is the root lifecycle class; Panel, panel containers,
toolbars, toast stacks, and app-specific panel-managed controls extend it.
Managers
Managers keep application behavior and state outside rendering. Descriptor-backed managers can share the same definitions with panels so settings, shortcut help, and URL docs stay synchronized with the app.
- PanelManager mounts
PanelComponent[]without deck.gl - SettingsManager tracks
settings snapshots and persistence for
SettingsPanel - KeyboardShortcutsManager
dispatches shortcuts documented by
KeyboardShortcutsPanel - URLManager parses and serializes
deep links documented by
URLParametersPanel - CommandManager registers executable app commands
- Toast Manager owns toast state
rendered by
ToastComponent
Start with Using Panels for panel
composition, Using Components for
non-panel PanelComponent instances, and
Using Managers when application
descriptors should also drive panel help or settings UI.
The deck.gl-community repository is semi-maintained. One of its goals is to collect and preserve valuable deck.gl ecosystem related code that does not have a dedicated home. Some modules may no longer have dedicated maintainers. This means that there is sometimes no one who can respond quickly to issues.