Geometry
Rows and tabsets are CSS flex sized by weight. Panels are positioned by the engine. Content lives in a moveable element.
Dockable's geometry follows FlexLayout's, in three layers. Knowing them explains every styling rule on this site.
1. Rows and tabsets are flex
Dockable.Row renders display: flex (flex-direction: row, or column for a nested row),
and each child (a tabset or a nested row) is a flex item sized by its weight:
/* what Row and TabSet set, structurally */
flex-basis: 0;
flex-grow: calc(weight * 1000); /* at least 1 */
min-width / min-height / max-width / max-height: from the model;
overflow: hidden;A row with children of weight 60 and 40 splits its space 60/40, minus the splitters. Splitters
are flex items too: their measured thickness is the model's splitter size. Dragging a splitter
dispatches Actions.adjustWeights for its row, and the new flex-grow values re-flow the row.
This is plain CSS layout, so it responds to container size for free, and your borders, gaps and margins participate (a margin on a tabset takes space from its weight's share, as in any flex row).
The root row fills the root: position: absolute; inset: 0. That is why the root needs a size
(Sizing the root).
2. Panels are positioned by the engine
Tab content is not rendered inside the tabset. Dockable.TabSetContent is an empty element
that only marks the content area. After every commit (and whenever an observed element resizes)
the engine:
- measures every registered element (rows, tabsets, tab strips, content areas, tab buttons, splitters) and writes the rects into the model;
- positions each
Dockable.Panelover its tabset's content area:position: absoluteplusleft,top,width,height, relative to the root; - shows the selected tab's panel and hides the others with
display: none.
The panels live in a panel layer: they are portalled into the root, after its other children. That has consequences you style around:
- a tabset's
border-radiusoroverflow: hiddendoes not clip its panel (they are siblings, not parent and child). Repeat the radius on the panel, or use square tabsets (Limitations, gap 2); - a panel paints above earlier absolutely positioned siblings, including the drop indicator,
unless you give those a
z-index(gap 12); - a panel cannot be styled from its tabset's state with a descendant selector.
The engine writes this geometry directly to the DOM. React never reconciles it, so a resize or a live splitter drag repositions panels without re-rendering anything.
3. Content lives in a moveable element
Each tab gets one moveable element, a <div data-dockable-moveable> sized 100% by 100%.
Dockable.Panel portals your content into it, and the engine appends the moveable element into
whichever panel is current with appendChild.
When a tab moves (to another tabset, into a popout window, back), the engine re-parents the
same element. It is never cloned, and the React tree that rendered the content never changes
position (it always lives under Dockable.Panels), so:
- component state, uncontrolled inputs, focus-independent DOM state, canvases, iframes' parents and scroll positions survive a move;
useEffectcleanups do not run on a move: the content is not unmounted.
When a panel goes away (its tabset is not rendered for a moment), the moveable element is parked in a hidden element in the root, so its DOM is never destroyed.
The one thing that resets on a move is anything the browser itself resets on re-parenting: an
<iframe> reloads when it is moved in the DOM. Keep iframes in tabs that do not move, or accept
the reload.
Render on demand
Tabs render their content lazily: Dockable.Panels renders a tab once it has been selected and
its content area has a size, and keeps it rendered afterwards. Set enableRenderOnDemand: false
on a tab to render it immediately, even while hidden.
enableWindowReMount: true opts a tab out of state preservation across windows: its content
remounts when it moves into or out of a popout.
Why it is built this way
Rendering content inside the tabset would tie the content's position in the React tree to the layout's structure, and every move would unmount and remount it. Positioning panels over measured areas keeps the layout and the content independent: the structure re-renders freely, and the content never notices.