@aptre/flex-layout

中文文档
0.6.4发布于 2026/04/21ISCTypeScriptES Module零依赖构建来源已验证近 30 天下载 2.2万

A multi-tab docking layout manager

原始使用文档
文档目录19 节
  1. Demo
  2. Installation
  3. Usage
  4. Example Configuration:
  5. Example Code
  6. OptimizedLayout
  7. Key Benefits
  8. How It Works
  9. Example Usage
  10. Props
  11. When to Use OptimizedLayout
  12. Model JSON Structure
  13. Model Actions
  14. Examples
  15. Optional Layout Props
  16. JSON Model Definition
  17. Layout Component Methods to Create New Tabs
  18. Tab Node Events
  19. Alternative Layout Managers

FlexLayout

npm Build status GitHub Website

FlexLayout is a layout manager supporting multiple TabSets with draggable+resizable tabs.

FlexLayout Demo Screenshot

This is a feature-fork of FlexLayout by Caplin. The original project hasn't been updated in some time, so this fork includes bug fixes, performance improvements, and new features like OptimizedLayout.

npm install @aptre/flex-layout
yarn add @aptre/flex-layout
pnpm add @aptre/flex-layout
bun add @aptre/flex-layout

FlexLayout's only dependency is React.

Features:

  • splitters
  • tabs
  • tab dragging and ordering
  • tab set dragging (move all the tabs in a tab set in one operation)
  • dock to tab set or edge of frame
  • maximize tab set (double click tab set header or use icon)
  • tab overflow (show menu when tabs overflow, scroll tabs using mouse wheel)
  • border tab sets
  • tab renaming (double click tab text to rename)
  • theming - light, underline, gray, round and dark
  • works on mobile devices (iPad, Android)
  • add tabs using drag, add to active tab set, add to tab set by id
  • tab and tab set attributes: enableTabStrip, enableDock, enableDrop...
  • customizable tabs and tab set rendering
  • component state is preserved when tabs are moved
  • typescript type declarations
  • OptimizedLayout - renders tab content outside FlexLayout's DOM for better performance

Demo

Try the interactive demo at flexlayout.spacewave.app.

API documentation is available at flexlayout.spacewave.app/typedoc/.

To run the tests locally, clone this repo, then:

bun install
bun run test:browser

Installation

FlexLayout is in the npm repository. install using:

npm install @aptre/flex-layout

Import FlexLayout in your modules:

import {Layout, Model} from '@aptre/flex-layout';

Include the light, underline, gray or dark theme by either:

Adding an additional import:

import '@aptre/flex-layout/style/light.css';

or by adding the css to your html:

<link rel="stylesheet" href="node_modules/@aptre/flex-layout/style/light.css" />

Usage

The <Layout> component renders the tab sets and splitters, it takes the following props:

Required props:

PropDescription
modelthe layout model
factorya factory function for creating React components

Additional optional props

The model is tree of Node objects that define the structure of the layout.

The factory is a function that takes a Node object and returns a React component that should be hosted by a tab in the layout.

The model can be created using the Model.fromJson(jsonObject) static method, and can be saved using the model.toJson() method.

Example Configuration:

var json = {
    global: {},
    borders: [],
    layout: {
        type: "row",
        weight: 100,
        children: [
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "One",
                        component: "button",
                    },
                ],
            },
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "Two",
                        component: "button",
                    },
                ],
            },
        ],
    },
};

Example Code

const model = Model.fromJson(json);

function App() {
    const factory = (node) => {
        var component = node.getComponent();

        if (component === "button") {
            return <button>{node.getName()}</button>;
        }
    };

    return <Layout model={model} factory={factory} />;
}

The above code would render two tab sets horizontally each containing a single tab that hosts a button component. The tabs could be moved and resized by dragging and dropping. Additional grids could be added to the layout by sending actions to the model.

OptimizedLayout

OptimizedLayout is a wrapper around Layout that renders tab content outside of FlexLayout's DOM structure for better performance. This is particularly useful for complex tab content that shouldn't re-render when the layout model changes.

Key Benefits

  1. No re-renders on layout changes - Tab components are NOT re-rendered when the Model changes
  2. State preservation - Tab state (scroll position, form inputs, etc.) is preserved across layout mutations
  3. CSS-only updates - Only CSS properties change when layout changes, avoiding React re-renders

How It Works

  1. OptimizedLayout renders Layout with lightweight TabRef placeholders instead of actual tab content
  2. TabRef components listen to resize/visibility events from TabNodes
  3. A sibling TabContainer renders the actual tab content with absolute positioning
  4. During drag operations, TabContainer uses pointer-events: none to prevent interfering with FlexLayout's drag overlay

Example Usage

import { Model, OptimizedLayout } from "@aptre/flex-layout";
import "@aptre/flex-layout/style/light.css";

const json = {
    global: {},
    borders: [],
    layout: {
        type: "row",
        weight: 100,
        children: [
            {
                type: "tabset",
                weight: 50,
                children: [{ type: "tab", name: "Editor", component: "editor" }],
            },
            {
                type: "tabset",
                weight: 50,
                children: [{ type: "tab", name: "Preview", component: "preview" }],
            },
        ],
    },
};

const model = Model.fromJson(json);

function App() {
    // renderTab receives a TabNode and returns the content for that tab
    const renderTab = (node) => {
        const component = node.getComponent();

        if (component === "editor") {
            return <Editor />;
        }
        if (component === "preview") {
            return <Preview />;
        }
        return <div>Unknown component: {component}</div>;
    };

    return (
        <div style={{ position: "relative", width: "100%", height: "100vh" }}>
            <OptimizedLayout model={model} renderTab={renderTab} />
        </div>
    );
}

Props

OptimizedLayout accepts all the same props as Layout, except:

  • renderTab (required) - Replaces factory. A function that receives a TabNode and returns a React element to render as the tab content.

All other Layout props (model, onModelChange, classNameMapper, etc.) work the same way.

When to Use OptimizedLayout

Use OptimizedLayout when:

  • Your tab content is expensive to render
  • You need to preserve internal component state across layout changes
  • You have many tabs and want to minimize re-renders

Use the standard Layout when:

  • You need tab content to re-render when the model changes
  • Your tab content is simple and lightweight
  • You need the factory pattern for lazy loading components

Model JSON Structure

The model json contains 3 top level elements:

  • global - (optional) where global options are defined
  • layout - where the main row/tabset/tabs layout hierarchy is defined
  • borders - (optional) where up to 4 borders are defined ("top", "bottom", "left", "right")

The layout element is built up using 3 types of 'node':

  • row - rows contains a list of tabsets and child rows, the top level 'row' will render horizontally (unless the global attribute rootOrientationVertical is set) , child 'rows' will render in the opposite orientation to their parent row.

  • tabset - tabsets contain a list of tabs and the index of the selected tab

  • tab - tabs specify the name of the component that they should host (that will be loaded via the factory) and the text of the actual tab.

The layout structure is defined with rows within rows that contain tabsets that themselves contain tabs.

The optional borders element is made up of border nodes

  • border - borders contain a list of tabs and the index of the selected tab, they can only be used in the borders top level element.

The tree structure for the JSON model is well defined as Typescript interfaces, see JSON Model

Each type of node has a defined set of requires/optional attributes.

Weights on rows and tabsets specify the relative weight of these nodes within the parent row, the actual values do not matter just their relative values (ie two tabsets of weights 30,70 would render the same if they had weights of 3,7).

By changing global or node attributes you can change the layout appearance and functionality, for example:

Setting tabSetEnableTabStrip:false in the global options would change the layout into a multi-splitter (without tabs or drag and drop).

 global: {tabSetEnableTabStrip:false},

Model Actions

Once the model json has been loaded all changes to the model are applied through actions.

You apply actions using the Model.doAction() method.

This method takes a single argument, created by one of the action generators (typically accessed as FlexLayout.Actions.<actionName>).

See Actions.ts for available actions.

Examples

model.doAction(
    FlexLayout.Actions.updateModelAttributes({
        splitterSize: 40,
    }),
);

The above example would increase the size of the splitters, this could be used to make adjusting the layout easier on a small device.

model.doAction(FlexLayout.Actions.addNode({ type: "tab", component: "grid", name: "a grid", id: "5" }, "1", FlexLayout.DockLocation.CENTER, 0));

This example adds a new grid component to the center of tabset with id "1" and at the 0'th tab position (use value -1 to add to the end of the tabs).

Note: you can get the id of a node (e.g., the node returned by the addNode action) using the method node.getId(). If an id wasn't assigned when the node was created, then one will be created for you of the form #<uuid> (e.g. #0c459064-8dee-444e-8636-eb9ab910fb27).

Note: You can intercept actions resulting from GUI changes before they are applied by implementing the onAction callback property of the Layout.

Optional Layout Props

There are many optional properties that can be applied to the layout. See ILayoutProps in Layout.tsx for the full interface.

JSON Model Definition

The JSON model is well defined as a set of TypeScript interfaces. See IJsonModel.ts for all available attributes:

  • IJsonModel - Top-level model structure
  • IGlobalAttributes - Global config attributes
  • IJsonRowNode - Row config attributes
  • IJsonTabSetNode - Tab set config attributes (note: tab sets are dynamically created as tabs are moved, and deleted when empty unless enableDeleteWhenEmpty is false)
  • IJsonTabNode - Tab config attributes
  • IJsonBorderNode - Border config attributes

Layout Component Methods to Create New Tabs

There are methods on the Layout Component for adding tabs. See Layout.tsx for available methods.

Example:

layoutRef.current.addTabToTabSet("NAVIGATION", {type:"tab", component:"grid", name:"a grid"});

This would add a new grid component to the tab set with id "NAVIGATION" (where layoutRef is a ref to the Layout element, see https://react.dev/learn/referencing-values-with-refs ).

Tab Node Events

You can handle events on nodes by adding a listener, this would typically be done when the component is mounted in a useEffect method:

Example:

    function MyComponent({node}) {

      useEffect(() => {
        // save subject in flexlayout node tree
        node.setEventListener("save", () => {
             node.getConfig().subject = subject;
           };
        }, []);
    }

EventparametersDescription
resize{rect}called when tab is resized during layout, called before it is rendered with the new size
closenonecalled when a tab is closed
visibility{visible}called when the visibility of a tab changes
savenonecalled before a tabnode is serialized to json, use to save node config by adding data to the object returned by node.getConfig()

Alternative Layout Managers

NameRepository
rc-dockhttps://github.com/ticlo/rc-dock
Dockviewhttps://dockview.dev/
luminohttps://github.com/jupyterlab/lumino
golden-layouthttps://github.com/golden-layout/golden-layout
react-mosaichttps://github.com/nomcopter/react-mosaic

版本与发布记录

@aptre/flex-layout 共发布 49 个版本,首次发布于 2023/10/19;过去 12 个月发布了 22 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.4latest2026/04/216 个月前
0.6.32026/04/166 个月前
0.6.22026/02/118 个月前
0.6.12026/02/118 个月前
0.6.02026/02/118 个月前
0.5.132026/02/118 个月前

其他发布通道:beta → 0.3.0-beta.5。

关于 @aptre/flex-layout 的常见问题

@aptre/flex-layout 是什么?

A multi-tab docking layout manager

如何安装 @aptre/flex-layout?

在项目目录运行 npm install @aptre/flex-layout,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.2.5)、react-dom(^19.2.5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@aptre/flex-layout 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aptre/flex-layout 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aptre/flex-layout 可以商用吗?

@aptre/flex-layout 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@aptre/flex-layout 的下载量有多少?

根据 npm 官方下载统计,@aptre/flex-layout 在 2026/10/07 – 2026/11/05 这 30 天内被下载 21,931 次,日均约 731 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aptre/flex-layout 还在维护吗?

最新版本 0.6.4 发布于 2026/04/21(6 个月前);至今共发布 49 个版本,其中过去 12 个月发布了 22 个;首次发布于 2023/10/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。