Introduction
A headless layout manager for dockable panels. It ships behaviour and accessibility, never styling.
Dockable is a layout manager for dockable panels: tabs grouped in tabsets, tabsets laid out in rows and columns, splitters between them, drag and drop to rearrange everything, and popout windows. It is headless: it gives you the behaviour, the accessibility and a set of composable primitives, and you decide what every pixel looks like.
What "headless" means here
The package does the hard parts and nothing else:
- a model of the layout: an immutable, JSON-shaped tree of rows, tabsets and tabs, with your own typed data on each tab, that changes only through commands your middleware can veto or rewrite;
- a layout engine that measures the elements you render, positions the tab panels over their tabsets and keeps tab content alive while it moves between tabsets and windows;
- drag and drop with hit-testing, drop locations and a drop indicator state;
- splitters with pointer and keyboard resizing;
- popout windows that carry live content into a second browser window;
- JSON serialization with validation, a command catalogue that describes itself (names, descriptions, JSON Schemas: ready for automation and AI tools), and the WAI-ARIA patterns for tabs and separators.
It ships no CSS, no icons, no rendered menus and no text:
| never shipped | what you do instead |
|---|---|
| CSS, class names, themes | style the primitives with className/style (a value or a function of the state), and the data-* attributes they expose. Tailwind, CSS modules, plain CSS: nothing assumes one. See Styling with plain CSS. |
| Icons | every button is yours. Close, maximize or pop out buttons are plain elements that run commands. |
| Menus | the package gives you the commands; you render the menu with your own components. See Menus. |
| App policy | no undo/redo, no persistence, no translations. The model exposes its commands and its events, and the guides show how to build undo/redo and persistence on them. |
| Text | a primitive renders only its children. Accessible names come from you (aria-label, children, render). With none, no text and no name is rendered. |
The only inline styles a primitive applies are structural: position, geometry, display
and flex sizing. Everything cosmetic is yours. The primitive contract
spells out the rule.
The packages
| package | contains | depends on |
|---|---|---|
@fragiola/dockable | the framework-agnostic core: the model and its commands, JSON serialization and validation, drop hit-testing, splitter math, the measure-and-position cycle, the drag-and-drop machine, popout window lifecycle | nothing (DOM only) |
@fragiola/dockable-react | composable React primitives over the core (Dockable.Root, Row, TabSet, TabList, Tab, …), the hooks under them, and the whole core, re-exported: the one package a React app installs | the core (same version); react and react-dom ^19 (peers) |
Every piece of logic that is not rendering lives in the core, so adapters for other frameworks can follow without duplicating behaviour.
Where it comes from
Dockable is derived from FlexLayout
(flexlayout-react 0.11.0) by Caplin Systems Ltd, released under the MIT licence
(© 2017 Caplin Systems Ltd). The model is Dockable's own (a plain state tree, a command bus and
typed data), but its algorithms come from FlexLayout: docking, tidying and selection, the drop
hit-testing, the splitter math and the data-layout-path scheme are ported from it, and every
ported file names FlexLayout, Caplin Systems Ltd and the licence in its header. FlexLayout's tests
remain the behaviour reference. The view is rewritten as headless primitives; FlexLayout's own
markup, class names and default strings are not copied. Thank you to the FlexLayout authors: this
project stands on their work.
If you know FlexLayout, the behaviour will be familiar: the same drops, splits and selection
rules. What changes is the surface: plain, typed JSON and named commands instead of a tree of
node classes, and, instead of one <Layout factory={…} /> component with a theme, primitives you
compose and markup you own.