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
| # | missing | workaround (what the examples do) | removed by |
|---|---|---|---|
| 1 | Tab 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 |
| 2 | Panels 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 |
| 3 | popoutMirrorRoot on Root mirrors <html> and <body> attributes. See Popouts. | None needed. | Epic #20 (done) |
| 4 | Flipping 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 |
| 5 | In 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 |
| 6 | Styling 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 |
| 7 | Dockable.PopoutTrigger. See Popouts. | None needed. | Epic #20 (done) |
| 8 | 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) |
| 9 | No 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 |
| 10 | A 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 |
| 11 | 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) |
| 12 | The 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
| feature | status | removed 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 invisible | not scheduled yet |
| Tab groups (pills grouping tabs in a strip) | model only; no primitive renders groups, in tabsets or borders | not scheduled yet |
Border extras: enableTabScrollbar, and float/popout buttons in a border's strip | not rendered; a border's strip is yours to fill (Dockable.PopoutTrigger works in tabsets only) | not scheduled yet |
Inline rename on F2 | the 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 Vue | the core is framework-agnostic so they can follow | not 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:
leftmeans the left side on screen, which is the end in RTL (gap 5); - flipping
dirat runtime needs a manualengine.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 ifonActionvetoes 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
TabSetContentbefore theTabList), not a style:TabSetis 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 insideDockable.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. Seewidget-sidebar. - An input inside a tab gets the tab's keyboard handling and dragging: stop key propagation
and set
draggable={false}while editing. Seerename-tabs. renderfunctions and refs:RenderedProps.refis typed for anyHTMLElement, 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.