Description:
React-Dockable is a React library for dockable, tabbed workspaces in editors, dashboards, and browser-based tools. Users rearrange tabs and resize panels, while a declarative component tree defines the initial layout.
Layouts can remain internally managed or move into React state for persistence. Four color themes, panel spacing, and window corner radii control the workspace appearance.
Preview

How To Use It
Install the package with NPM.
npm install @danfessler/react-dockableImport the stylesheet and give the workspace an explicit height. Each tab needs a unique, stable id.
import { Dockable } from "@danfessler/react-dockable";
import "@danfessler/react-dockable/style.css";
export function Workspace() {
return (
<div style={{ height: "80vh" }}>
<Dockable.Root theme="dark" gap={6} radius={8}>
<Dockable.Tab id="files" name="Files">
<p>Project files</p>
</Dockable.Tab>
<Dockable.Tab id="preview" name="Preview">
<p>Document preview</p>
</Dockable.Tab>
<Dockable.Tab id="inspector" name="Inspector">
<p>Selection properties</p>
</Dockable.Tab>
</Dockable.Root>
</div>
);
}Define a Nested Layout
Panel splits space, Window groups tabs, and Tab supplies content. A nested panel uses the opposite orientation from its parent unless you set orientation. Panel and window sizes use fractional units.
Treat these children as the initial layout and tab definitions. The library manages subsequent arrangement internally; use controlled layout state when your application needs to change or save the arrangement.
<Dockable.Root orientation="row">
<Dockable.Tab id="files" name="Files">
<p>Project files</p>
</Dockable.Tab>
<Dockable.Panel size={3}>
<Dockable.Tab id="preview" name="Preview">
<p>Document preview</p>
</Dockable.Tab>
<Dockable.Window selected={0}>
<Dockable.Tab id="console" name="Console">
<p>Console output</p>
</Dockable.Tab>
<Dockable.Tab id="problems" name="Problems">
<p>Validation results</p>
</Dockable.Tab>
</Dockable.Window>
</Dockable.Panel>
</Dockable.Root>Persist the User’s Layout
useDockableLocalStorage reads localStorage during render. Mount a component that uses it only in the browser. In Next.js, load this workspace from a Client Component with dynamic(..., { ssr: false }).
Increase the version argument whenever your tab definitions change. A restored layout that refers to a missing tab ID causes an exception.
The hook uses the storage key layout. For multiple independent workspaces on one origin, manage layout and onChange yourself with workspace-specific storage keys. Use onChange to persist layouts to a backend when needed.
The hook does not catch malformed stored JSON or storage access failures. Add error handling in your own persistence adapter if those cases must recover gracefully.
import {
Dockable,
useDockableLocalStorage,
} from "@danfessler/react-dockable";
import "@danfessler/react-dockable/style.css";
export function SavedWorkspace() {
const { layout, setLayout } = useDockableLocalStorage(1);
return (
<div style={{ height: "80vh" }}>
<Dockable.Root layout={layout} onChange={setLayout} theme="dark">
<Dockable.Tab id="editor" name="Editor">
<textarea aria-label="Document" defaultValue="Project notes" />
</Dockable.Tab>
<Dockable.Tab id="preview" name="Preview">
<p>Document preview</p>
</Dockable.Tab>
</Dockable.Root>
</div>
);
}Layout API
| Component | Key configuration |
|---|---|
Dockable.Root | children for the initial layout; orientation, layout, onChange, gap, radius, and theme |
Dockable.Panel | Layout children, orientation, and proportional size |
Dockable.Window | Tab children, proportional size, and initial tab index selected |
Dockable.Tab | Unique id, display name, children content, and optional actions |
Dockable.Menu | Required id: string, customItems, children, and mode (dropdown by default, or context) |
useDockableLocalStorage(version) | Return layout and setLayout; version changes reset the saved arrangement |
A LayoutNode is a PanelNode or WindowNode; the saved layout is an array of these nodes.
The saved selected ID differs from the initial numeric Dockable.Window selected prop. Keep tab IDs unique and present in the saved layout’s tab definitions.
| Node | Fields |
|---|---|
| Panel | id: string, type: "Panel", children: LayoutNode[], optional orientation (row or column) and proportional size |
| Window | id: string, type: "Window", children: string[] of tab IDs, selected: string tab ID, and optional proportional size |
Tab Actions and Menus
Pass actions an array of sections, each with an optional label and an items array. Action items have label, onClick, optional shortcut, and optional disabled; submenu items contain another items array.
<Dockable.Tab
id="preview"
name="Preview"
actions={[
{
items: [
{ label: "Refresh", onClick: () => console.log("Refresh preview") },
{ label: "Export", onClick: () => {}, disabled: true },
],
},
]}
>
<p>Document preview</p>
</Dockable.Tab>Custom Menu Trigger
Use Dockable.Menu for a custom trigger, with customItems in that section format. Supply id to satisfy its published TypeScript props and provide both trigger children and customItems; the menu renders nothing if either is absent. shortcut displays a label and does not register a keyboard shortcut.
<Dockable.Menu
id="preview-actions"
customItems={[
{ items: [{ label: "Refresh", onClick: () => console.log("Refresh") }] },
]}
>
<button type="button">Preview actions</button>
</Dockable.Menu>Themes and Styling
Choose light, medium, dark, or darker. The default follows the device’s light/dark preference. Set gap for panel spacing and radius for window corners.





