flexlayout-react: a multi-tab docking layout manager for React 18/19 (only peer dependency). MIT licensed, ESM-only. You render <Layout model={model} factory={factory} /> driven by a JSON model tree; the model is the single source of truth and the UI is derived from it. CSS themes ship as flexlayout-react/style/<theme>.css (alpha_light, alpha_dark, alpha_rounded, light, dark, underline, gray, rounded, aria, combined).
Before claiming a task complete, run and pass:
pnpm lint(eslint + generated-interface check)pnpm typecheckpnpm test(vitest, once)pnpm playwright/pnpm playwright:uifor changes touchingsrc/view/or drag/drop behavior- If you changed JSON model interfaces in
src/model/IJsonModel.ts:pnpm generate:interfaces(they are generated byscripts/generate-json-interfaces.ts;pnpm lintfails if they're stale)
- Never mutate model nodes directly. All changes go through
model.doAction(Actions.*)— no setting attributes, adding/removing children, etc. GUI edits also flow through actions (intercept via theonActionprop). - Only symbols exported from
src/index.tsare public API. Treat everything else as internal. - The
factorymust return a React node, dispatched onnode.getComponent(). Handle per-window effects vianode.getDocument()/node.getWindow()(seedemo/monaco.tsxfor the document-change remount pattern). - Popout windows render into a different document — libraries bound to the main
window/documentmay need remounting (see README "Popout Windows"). Component state is otherwise preserved when tabs move. - TypeScript is strict; Prettier and ESLint are enforced. Don't add stray comments. Follow existing code style; make minimal changes.
pnpm dev— demo app dev server (vite with HMR; the demo importssrc/directly).pnpm test— vitest single run (terminates).pnpm test:coveragefor coverage.pnpm playwright— Playwright e2e (headless chromium).pnpm playwright:uifor the UI runner.pnpm lint—check:interfaces+ eslint.pnpm format/pnpm format:check— prettier.pnpm typecheck—tsc -p tsconfig.json.pnpm build— full build: clean, generate interfaces, typecheck, demo, examples, css, lib, types, typedoc.pnpm dev:examples/pnpm build:examples— run/build theexamples/app (multi-page vite, root./examples/, one URL per example).examples/is covered bylint/typecheck/format.
- Model tree = serialized JSON via
Model.fromJson(json)/model.toJson(). - Node types in JSON:
row(children: rows + tabsets; orientation alternates per nesting level),tabset(children: tabs),tab(holds the component),border(only under the top-levelbordersarray:top/bottom/left/right). - Node classes:
RowNode,TabSetNode,TabNode,BorderNode, all extendingNode(id, attributes, listeners). - Attribute inheritance: node attributes default from
global.*prefixed by node type —tabXxx,tabSetXxx,borderXxx(e.g.global.tabEnableClosevs per-tabenableClose). Seesrc/model/Attributes.ts. - Tabsets are created/destroyed dynamically as tabs move (unless
enableDeleteWhenEmpty: false). <Layout>fills its parent container, which must beposition: absoluteorrelative.
Full signatures are in src/model/Actions.ts; commonly needed ones:
- Add a tab:
Actions.addTab(jsonTabNode, toTabsetId, DockLocation.CENTER, index, select?)—index -1= append.Actions.addNodehas the same signature. - Move/remove/rename:
Actions.moveNode,deleteTab,deleteTabset,renameTab. - Attributes at runtime:
Actions.updateNodeAttributes(nodeId, attrs); model-levelActions.updateModelAttributes(attrs). - Tabsets/borders:
Actions.maximizeToggle(tabsetId, layoutId?),Actions.setBorderType(borderId, "split" | "overlay"),Actions.adjustWeights,Actions.adjustBorderSplit. - Popouts/floating:
Actions.popoutTab(id, type)/popoutTabset(id, type)withtype: "window" | "float";createPopout,createSubLayout,moveFloat,closePopout,movePopoutToFront. - Undo/redo:
useUndo(initialModelOrFactory)hook (src/view/useUndo.ts) — owns model state, snapshots before mutations (an entire drag = one step), swaps models viaModel.fromJsonto preserve mounted content. - Layout ref API (
ILayoutApiviauseRef<Layout>):addTabToTabSet(id, jsonTabNode),redraw(), etc. - Node events (
node.setEventListener):resize,close,visibility,save(persistnode.getConfig()before serialization).
- Public
<Layout>component +ILayoutProps/ILayoutApi→src/view/Layout.tsx; callback/option types (ITabRenderValues,ITabSetRenderValues,IIcons,IKeyMap,defaultKeyMap) →src/view/layout/LayoutTypes.ts. - Rendering engine →
src/view/layout/LayoutController.tsx(LayoutController) +src/view/layout/LayoutInternal.tsx(view wrapper, edge drop rects). - Docking / drag-and-drop / resizing →
src/view/layout/DragDropManager.tsx,src/view/Splitter.tsx,src/model/DropInfo.ts,IDraggable.ts,IDropTarget.ts. - Popout windows →
src/view/PopoutWindow.tsx(React portals; requires apopout.htmlhost page); floating panels →src/view/FloatWindow.tsx,src/view/layout/FloatingWindowContainer.tsx. - Border tabsets →
src/view/BorderTabSet.tsx,BorderTab.tsx,BorderButton.tsx,src/view/layout/BorderContainer.tsx; overlay behavior →src/view/Overlay.tsx. - Tabs/tabstrip →
src/view/Tab.tsx,TabButton.tsx,TabLayout.tsx,TabContentRenderer.tsx,TabOverflowHook.tsx,DragTabButton.tsx,TabButtonStamp.tsx. - Customization hooks →
onRenderTab/onRenderTabSetLayout props; custom icons viaiconsprop andsrc/view/Icons.tsx; class remapping viaclassNameMapperand theCLASSESenum insrc/CSSClassNames.ts. - Context/overflow menus →
src/view/PopupMenu.tsx,src/view/ContextMenuBuilder.tsx(showPopupMenu). - Accessibility / keyboard →
keyMapprop +defaultKeyMap; ARIA roles in view components and themes. - i18n →
i18nTranslatorprop on<Layout>; default English text inI18nLabelDefaults; keys insrc/view/I18nLabel.ts. - Data model (no React) →
src/model/:Model.ts(serialization),Node.ts+ subclasses,Actions.ts,IJsonModel.ts,Attributes.ts,BorderSet.ts,ModelLayout.ts(popout/float sub-layout),Rect.ts,Orientation.ts,DockLocation.ts.
The examples/ directory is a multi-page vite app (pnpm dev:examples), one self-contained example per feature at its own URL (see the landing page examples/index.html for the full list). Each example is a single .tsx importing only the public API and the combined stylesheet — they are the canonical reference for common features (borders, tab/tabset rendering, context menus, min sizes, pinned tabs, sticky buttons, tab wrapping, external drag, many tabs, undo/redo, localstorage persistence, empty-tabset placeholders, popouts, theming). To add one: create examples/<name>/{index.html, Name.tsx}, register it in examples/vite.config.examples.ts and link it from the landing page. All examples are covered by lint/typecheck/format.
- All themable values are CSS custom properties defined in
style/_themes.scss(--fl-color-*,--fl-font-*,--fl-splitter-size,--fl-tab-button-radius, etc.), each asvar(--flexlayout-<name>, <theme default>). Override the global--flexlayout-<name>variables (on:root, a container, or the layout root) to restyle every theme/layout — including floats, sublayouts and popouts — without an scss rebuild. - Full themes: import one theme css, or
combined.cssfor runtime switching via aflexlayout__theme_<name>class on the container. - Scss sources/mixins:
style/_themes.scss,style/_base.scss; compiled css is committed alongside.
tests/— vitest unit tests (model, actions, serialization,useUndo, view components undertests/view/).tests-playwright/— Playwright e2e;tests-playwright/helpers.tshas ready-made helpers. Layout fixtures live indemo/public/layouts/*.layoutand are loaded by the demo app the e2e tests drive.- e2e selectors: every rendered element carries
data-layout-path, e.g./r1/ts0(row 1, tabset 0),/border/left,/ts0/tb1(tab button 1),/ts0/g0(group pill),/ts0/t0(tab panel 0),/s0(splitter),/button/max,/popup-menu. Paths reflect the current tree, so indices shift after moves.
README.md— authoritative usage guide: model JSON examples, attribute inheritance, themes, popout windows (incl. CSP/secure-env caveats), accessibility tables,data-layout-pathtable.typedoc/— generated API docs (pnpm doc).CHANGELOG.md— release history.demo/— reference app and working patterns:App.tsx, plus showcasesmonaco.tsx(popout remounting),aggrid.tsx,MUIDataGrid.tsx,chart.tsx,openlayer.tsx,terminal.tsx,SimpleForm.tsx,ModelExplorer.tsx,JsonView.tsx,AttributeEditor.tsx,ThemePanel.tsx,ActionLog.tsx.examples/— runnable self-contained example apps (one per feature), each a multi-page vite entry atexamples/<name>/{index.html, X.tsx}(e.g.basic,borders,context-menu,undo-redo,pinned-tabs). They import only the public API (../../src/index) and the theme styles (../../style/combined.scss), shareexamples/Header.tsx(title + GitHub source link), and are registered inexamples/vite.config.examples.ts. These are the canonical "how do I use feature X" references; add new examples there rather thandemo/.