Dockable

Known limitations

What is not there yet, the workaround the examples use, and the Epic that removes it.

Dockable is young. This page lists, honestly, what the public surface is missing today. The numbered gaps come from the project's walking-skeleton report (section 2), with the same numbers, so issues and example comments can refer to them ("gap 10"). Each has the workaround the examples use and the Epic that will remove it, when one is scheduled.

The gaps

#missingworkaround (what the examples do)removed by
1Tab knows selected but not whether its tabset is active, so "the selected tab of the active tabset" has no attribute.An ancestor selector: .tabset[data-active] .tab[data-selected], or Tailwind's in-data-active: / group-data-active: on a child of the tab.not scheduled yet
2Panels live outside their tabset (in the panel layer), so a tabset's border-radius/overflow cannot clip them, and a panel cannot be styled by its tabset's state.Rounded tabsets repeat the tabset's inner radius on the panel's bottom corners (it breaks if the strip moves to the bottom), or use square tabsets. The active tabset is marked on its selected tab instead of with a tabset-wide ring.not scheduled yet
3A popout document does not get the page's root attributes and body classes. Solved: popoutMirrorRoot on Root mirrors <html> and <body> attributes. See Popouts.None needed.Epic #20 (done)
4Flipping dir at runtime moves every tabset without resizing any, so the engine's observers never fire and panels stay at their old positions.Call engine.sync() after changing dir.not scheduled yet
5In RTL, splitter drags and keyboard resizing are wrong (a drag jumps the first tabset to full width, arrows do nothing), and edge and side drops are physical (left docks before, whichever side that renders on).None. RTL renders correctly, but do not ship an RTL layout that users resize or rearrange.not scheduled yet
6Styling every splitter the same way needs renderSplitter on every Row (root, nested, and inside Popout).Pass renderSplitter to every Row (the examples' kit builds the recursion once), or style [role="separator"] from a stylesheet.not scheduled yet
7A pop out button for a tabset's selected tab has no primitive. Solved: Dockable.PopoutTrigger. See Popouts.None needed.Epic #20 (done)
8No drop-target state on the tabset under the pointer. Solved: data-drop-target and data-drop-location on TabSet, data-drop-target and data-drop-index on TabList. See Drop zones and drop feedback.None needed.Epic #19 (done)
9No way to make a splitter's hit area wider than its visible thickness.A 1px splitter with a centred ::after grab area, relative z-10; the engine measures the 1px element, so the split maths is exact. See Splitters.not scheduled yet
10A drop before the first tab is refused when that tab is flush with the tabset's edge (the edge belongs to the tabset's side drop).Pad the start of the tab list (padding-inline-start, ps-1 or more).not scheduled yet
11A thin tab strip at the top of the layout falls inside the 10px top-edge docking band. Solved: the band is the global edgeDockMargin (lower it for a thin strip), and Dockable.EdgeIndicator shows the bands during a drag. See Borders.Keep strips about 30px tall, or set edgeDockMargin.Epic #21 (done)
12The drop indicator paints under the panels: panels are portalled into the root after its other children.A z-index on the indicator (z-10).not scheduled yet

Features not built yet

featurestatusremoved by
Floats (in-page floating windows)not rendered. The model keeps "float" layouts, so avoid popoutTab(id, "float") and the "float" popout close policy: a floated layout is invisiblenot scheduled yet
Tab groups (pills grouping tabs in a strip)model only; no primitive renders groups, in tabsets or bordersnot scheduled yet
Border extras: enableTabScrollbar, and float/popout buttons in a border's stripnot rendered; a border's strip is yours to fill (Dockable.PopoutTrigger works in tabsets only)not scheduled yet
Inline rename on F2the renameTab binding exists in the key map but the primitives do not act on it; build the rename UI (Tabs)not scheduled yet
Adapters for Angular and Vuethe core is framework-agnostic so they can follownot scheduled yet

RTL

Dockable renders right-to-left correctly (the rows mirror, the tab strips run from the right), but interaction is not RTL-aware yet:

  • splitter drags and arrow-key resizing are wrong in RTL (gap 5);
  • drop locations are physical: left means the left side on screen, which is the end in RTL (gap 5);
  • flipping dir at runtime needs a manual engine.sync() (gap 4).

The fix (the core reading the row's direction, logical start/end drop locations) is not scheduled yet. Until then, treat RTL layouts as display-only.

Popout edge cases

  • A window layout can end up with no window and no renderer: with the "float" close policy a closed popout becomes a float, which is not rendered; with the default "dock" policy the same happens if onAction vetoes the dock-back moves. Either way its tabs are unreachable until the layout is reset. Do not veto moves while a popout closes.
  • The browser may block a popout that is reopened without a user gesture (for example when a saved layout with windows is restored at start-up).

Other things to know

  • The root needs a size (by design): an unsized root renders nothing visible. See Sizing the root.
  • A bottom tab strip is a markup change (render TabSetContent before the TabList), not a style: TabSet is a structural flex column.
  • The label map (Partial<Record<DockableLabel, string>>) is kept complete by hand as new parts add keys.

Found while writing the examples

The examples hit a few more rough edges. Each is worked around (and commented) in the example named, and all of them are listed in docs/docs-examples-gaps.md in the repository.

  • UI outside the root and the engine. useDockable() only works inside Dockable.Root. Outside it, LayoutEngine.of(model) returns the mounted engine; it changes when the model does, so read it when you act rather than keeping it. See widget-sidebar.
  • An input inside a tab gets the tab's keyboard handling and dragging: stop key propagation and set draggable={false} while editing. See rename-tabs.
  • render functions and refs: RenderedProps.ref is typed for any HTMLElement, so spreading the props onto a <div> needs a cast.

Found something else?

Open an issue. A gap you work around in your app is exactly what the next Epics need to hear about.

On this page