{"id":15823309,"url":"https://github.com/caplin/flexlayout","last_synced_at":"2026-04-25T11:04:12.970Z","repository":{"id":38360293,"uuid":"47620273","full_name":"caplin/FlexLayout","owner":"caplin","description":"Docking Layout Manager for React","archived":false,"fork":false,"pushed_at":"2025-04-25T18:51:22.000Z","size":24342,"stargazers_count":1069,"open_issues_count":172,"forks_count":192,"subscribers_count":35,"default_branch":"master","last_synced_at":"2025-04-28T17:14:24.310Z","etag":null,"topics":["dock","docking-layout","docking-library","drag","drag-and-drop","drop","layout","layout-manager","manager","react","reactjs","split","splitter","splitview","tabs","tabset","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/caplin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-12-08T12:19:06.000Z","updated_at":"2025-04-27T12:45:13.000Z","dependencies_parsed_at":"2023-02-06T11:31:02.710Z","dependency_job_id":"4d7bf145-7f3f-4299-8b6a-e1480361302f","html_url":"https://github.com/caplin/FlexLayout","commit_stats":{"total_commits":584,"total_committers":31,"mean_commits":"18.838709677419356","dds":0.3904109589041096,"last_synced_commit":"989112f98d37047f7e74011376e7812a081ce9e9"},"previous_names":[],"tags_count":92,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caplin%2FFlexLayout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caplin%2FFlexLayout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caplin%2FFlexLayout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caplin%2FFlexLayout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/caplin","download_url":"https://codeload.github.com/caplin/FlexLayout/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254029004,"owners_count":22002283,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dock","docking-layout","docking-library","drag","drag-and-drop","drop","layout","layout-manager","manager","react","reactjs","split","splitter","splitview","tabs","tabset","typescript"],"created_at":"2024-10-05T08:08:20.216Z","updated_at":"2026-04-25T11:04:12.908Z","avatar_url":"https://github.com/caplin.png","language":"TypeScript","readme":"# FlexLayout\n\n[![GitHub](https://img.shields.io/github/license/Caplin/FlexLayout)](https://github.com/caplin/FlexLayout/blob/master/LICENSE)\n![npm](https://img.shields.io/npm/dw/flexlayout-react)\n[![npm](https://img.shields.io/npm/v/flexlayout-react)](https://www.npmjs.com/package/flexlayout-react)\n\nFlexLayout is a layout manager for React that arranges components in multiple tabsets. Tabs can be resized, moved, and organized into complex layouts.\n\n![FlexLayout Demo Screenshot](screenshots/Screenshot_v0.9.png?raw=true \"FlexLayout Demo Screenshot\")\n\n[Run the Demo](https://caplin.github.io/FlexLayout/demos/v0.9/demo/index.html)\n\nTry it now using [CodeSandbox](https://codesandbox.io/p/sandbox/yvjzqf)\n\n[API Doc](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/index.html)\n\nFlexLayout's only dependency is React.\n\nFeatures:\n* Splitters for resizing\n* Tabs (scrolling or wrapped)\n* Tab dragging and ordering\n* Tabset dragging (move all tabs in a tabset in one operation)\n* Docking to tabsets or edges of the frame\n* Maximizing tabsets (double-click tabset header or use icon)\n* Tab overflow (menu for hidden tabs, mouse wheel scrolling)\n* Border tabsets\n* Popout tabs into floating panels or new browser windows\n* Submodels (layouts inside layouts)\n* Tab renaming (double-click tab text)\n* Theming (light, dark, underline, etc., and combined)\n* Mobile support (iPad, Android)\n* Multiple ways to add tabs (drag, active tabset, by ID)\n* Comprehensive tab and tabset attributes (`enableTabStrip`, `enableDock`, `enableDrop`, etc.)\n* Customizable tab and tabset rendering\n* Preservation of component state when tabs are moved\n* Playwright tests\n* TypeScript type declarations\n\n## Example Interaction\n![FlexLayout Animation](screenshots/Animation.gif?raw=true \"FlexLayout Animation\")\n\n## Installation\n\nFlexLayout is available on npm. Install it using:\n\n```bash\nnpm install flexlayout-react\n```\n\nImport FlexLayout and its model in your modules:\n\n```javascript\nimport { Layout, Model } from 'flexlayout-react';\n```\n\nInclude a theme. Choose from `alpha_light`, `alpha_dark`, `alpha_rounded`, `light`, `dark`, `underline`, `gray`, `rounded`, or `combined` (see the demo for examples):\n\n```css\nimport 'flexlayout-react/style/alpha_light.css';  \n```\n\n[Learn how to change the theme dynamically in code](#dynamically-changing-the-theme)\n\n\n## Usage\n\nThe `\u003cLayout\u003e` component renders the tabsets and splitters. It takes the following props:\n\n#### Required props:\n\n| Prop    | Description                                      |\n| ------- | ------------------------------------------------ |\n| `model` | The layout model                                 |\n| `factory` | A factory function for creating React components |\n\nAdditional [optional props](#optional-layout-props)\n\nThe model is a tree of `Node` objects that define the structure of the layout.\n\nThe factory is a function that takes a `Node` object and returns a React component to be hosted within a tab.\n\nModels can be created using the `Model.fromJson(jsonObject)` static method and saved using the `model.toJson()` method.\n\n## Example Configuration:\n\n```javascript\nconst json = {\n    global: {},\n    borders: [],\n    layout: {\n        type: \"row\",\n        weight: 100,\n        children: [\n            {\n                type: \"tabset\",\n                weight: 50,\n                children: [\n                    {\n                        type: \"tab\",\n                        name: \"One\",\n                        component: \"placeholder\",\n                    }\n                ]\n            },\n            {\n                type: \"tabset\",\n                weight: 50,\n                children: [\n                    {\n                        type: \"tab\",\n                        name: \"Two\",\n                        component: \"placeholder\",\n                    }\n                ]\n            }\n        ]\n    }\n};\n```\n\n## Example Code\n\n```javascript\nconst model = Model.fromJson(json);\n\nfunction App() {\n\n  const factory = (node) =\u003e {\n    const component = node.getComponent();\n\n    if (component === \"placeholder\") {\n      return \u003cdiv\u003e{node.getName()}\u003c/div\u003e;\n    }\n  }\n\n  return (\n    \u003cLayout\n      model={model}\n      factory={factory} /\u003e\n  );\n}\n```\t\t\n\nThe above code renders two tabsets horizontally, each containing a single tab that hosts a `div` component (returned from the factory). Tabs can be moved and resized by dragging and dropping. Additional tabs can be added to the layout by sending actions to the model.\n\n\u003cimg src=\"screenshots/Screenshot_two_tabs.png?raw=true\" alt=\"Simple layout\" title=\"Generated Layout\"/\u003e\n\nNote: The `\u003cLayout\u003e` component must be hosted in a container element (with CSS `position: absolute` or `relative`). The layout will fill the containing element.\n\n\nTry it now using [CodeSandbox](https://codesandbox.io/p/sandbox/yvjzqf)\n\nA simple TypeScript example can be found here:\n\nhttps://github.com/nealus/flexlayout-vite-example\n\nThe model JSON contains four top-level elements:\n\n* `global` - (optional) Global options.\n* `layout` - The main row/tabset/tabs layout hierarchy.\n* `borders` - (optional) Up to four borders (\"top\", \"bottom\", \"left\", \"right\").\n* `subLayouts` - (optional) Where sub layouts for popout windows, floating panels and tabs are defined.\n\nThe `layout` element is built using three types of nodes:\n\n* `row` - Rows contain a list of tabsets and child rows. The top-level `row` renders horizontally by default (unless the global attribute `rootOrientationVertical` is set). Child rows render in the opposite orientation to their parent row.\n* `tabset` - Tabsets contain a list of tabs and the index of the selected tab.\n* `tab` - Tabs specify the component to host (loaded via the factory) and the tab's display text.\n\nThe layout structure is defined with rows within rows that contain tabsets that themselves contain tabs.\n\nWithin the demo app, you can view the layout structure by checking the 'Show layout' box. Rows are shown in blue, and tabsets in orange.\n\n![FlexLayout Demo Showing Layout](screenshots/Screenshot_layout.png?raw=true \"Demo showing layout\")\n\nThe optional `borders` element is made up of border nodes:\n\n* `border` - Borders contain a list of tabs and the index of the selected tab. They can only be used within the `borders` top-level element.\n\nThe JSON model tree structure is defined as TypeScript interfaces; see [JSON Model](#json-model-definition).\n\nEach node type has a defined set of required and optional attributes.\n\nWeights on rows and tabsets specify their relative size within the parent row. The absolute values do not matter, only their proportions (e.g., two tabsets with weights 30 and 70 would render the same as if they had weights 3 and 7).\n\nNOTE: The easiest way to create your initial layout JSON is to use the [demo](https://caplin.github.io/FlexLayout/demos/v0.9/demo/index.html) app. Modify an existing layout by dragging, dropping, and adding nodes, then press the 'print' button to print the JSON to the browser's developer console.\n\nBy changing global or node attributes, you can modify the layout's 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):\n\n```\n global: {tabSetEnableTabStrip:false},\n```\n\n## Dynamically Changing the Theme\n\nThe `combined.css` theme includes all other themes and supports dynamic theme switching.\n\nWhen using `combined.css`, add a `className` (in the form `flexlayout__theme_[theme-name]`) to the `div` containing the `\u003cLayout\u003e` to select the desired theme.\n\nFor example: \n```\n    \u003cdiv ref={containerRef} className=\"flexlayout__theme_alpha_light\"\u003e\n        \u003cLayout model={model} factory={factory} /\u003e\n    \u003c/div\u003e\n```\n\nChange the theme in code by changing the className on the containing div.\n\nFor example:\n```\n    containerRef.current!.className = \"flexlayout__theme_alpha_dark\"\n```\n\n## Customizing Tabs\n\nYou can use the `\u003cLayout\u003e` prop `onRenderTab` to customize tab rendering:\n\n\u003cimg src=\"screenshots/Screenshot_customize_tab.png?raw=true\"\n     alt=\"FlexLayout Tab structure\"\n     title=\"Tab structure\"/\u003e\n\nUpdate the `renderValues` parameter as needed:\n\n`renderValues.leading`: The area shown in red.\n\n`renderValues.content`: The area shown in green.\n\n`renderValues.buttons`: The area shown in yellow.\n\nFor example:\n\n```\nonRenderTab = (node: TabNode, renderValues: ITabRenderValues) =\u003e {\n    // renderValues.leading = \u003cimg style={{width:\"1em\", height:\"1em\"}}src=\"images/folder.svg\"/\u003e;\n    // renderValues.content += \" *\";\n    renderValues.buttons.push(\u003cimg key=\"menu\" style={{width:\"1em\", height:\"1em\"}} src=\"images/menu.svg\"/\u003e);\n}\n```\n\n## Customizing Tabsets\n\nYou can use the `\u003cLayout\u003e` prop `onRenderTabSet` to customize tabset rendering:\n\n\u003cimg src=\"screenshots/Screenshot_customize_tabset.png?raw=true\"\n     alt=\"FlexLayout Tab structure\"\n     title=\"Tabset structure\" /\u003e\n\nUpdate the `renderValues` parameter as needed:\n\n`renderValues.leading`: The area shown in blue.\n\n`renderValues.stickyButtons`: The area shown in red.\n\n`renderValues.buttons`: The area shown in green.\n\n\nFor example:\n\n```\nonRenderTabSet = (node: (TabSetNode | BorderNode), renderValues: ITabSetRenderValues) =\u003e {\n    renderValues.stickyButtons.push(\n        \u003cbutton\n            key=\"Add\"\n            title=\"Add\"\n            className=\"flexlayout__tab_toolbar_button\"\n            onClick={() =\u003e {\n                model.doAction(Actions.addNode({\n                    component: \"placeholder\",\n                    name: \"Added \" + nextAddIndex.current++\n                }, node.getId(), DockLocation.CENTER, -1, true));\n            }}\n        \u003e\u003cAddIcon/\u003e\u003c/button\u003e);\n\n    renderValues.buttons.push(\u003cimg key=\"menu\" style={{width:\"1em\", height:\"1em\"}} src=\"images/menu.svg\"/\u003e);\n}\n```\n\n## Model Actions\n\nOnce the model JSON has been loaded, all changes are applied through actions. In the Demo app, you can view these actions in the 'Action Log':\n\n\u003cimg src=\"screenshots/Screenshot_action_log.png?raw=true\"\n     alt=\"Action Log\"\n     title=\"Action Log\" /\u003e\n\nApply actions using the `model.doAction()` method. This method takes a single argument created by one of the action generators (accessible via `FlexLayout.Actions.\u003cactionName\u003e`):\n\n[Actions Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/classes/Actions.html)\n\n### Example\n\n```js\nmodel.doAction(FlexLayout.Actions.addNode(\n    {type:\"tab\", component:\"grid\", name:\"a grid\", id:\"5\"},\n    \"1\", FlexLayout.DockLocation.CENTER, 0));\n```\n\nThis example adds a new grid component to the center of the tabset with ID \"1\" at the first position (0). Use `-1` to add to the end of the tabs.\n\nNote: You can retrieve the ID of a node (e.g., the node returned by the `addNode` action) using `node.getId()`. If an ID wasn't assigned when the node was created, one will be generated for you in the form `#\u003cuuid\u003e` (e.g., `#0c459064-8dee-444e-8636-eb9ab910fb27`).\n\nNote: You can intercept actions resulting from GUI changes before they are applied by implementing the `onAction` callback property of the `Layout`.\n\n## Optional Layout Props\n\nMany optional properties can be applied to the layout:\n\n[Layout Properties Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/ILayoutProps.html)\n\n\n## JSON Model Definition\n\nThe JSON model is defined as a set of TypeScript interfaces. See the documentation for details on allowed attributes:\n\n[Model Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IJsonModel.html)\n\n[Global Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IGlobalAttributes.html)\n\n[Row Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IJsonRowNode.html)\n\n[Tabset Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IJsonTabSetNode.html)\n\nNote: Tabsets are dynamically created as tabs are moved and deleted when their last tab is removed (unless `enableDeleteWhenEmpty` is set to `false`).\n\n[Tab Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IJsonTabNode.html)\n\n[Border Attributes Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/IJsonBorderNode.html)\n\n\n\n\n## Layout API Methods to Create New Tabs\n\nThe Layout Ref provides methods for adding tabs:\n\n[Layout Methods Documentation](https://caplin.github.io/FlexLayout/demos/v0.9/typedoc/interfaces/ILayoutApi.html)\n\nExample:\n\n```javascript\nlayoutRef.current.addTabToTabSet(\"NAVIGATION\", { type: \"tab\", component: \"grid\", name: \"a grid\" });\n```\nThis adds a new grid component to the tabset with ID \"NAVIGATION\". (where `layoutRef` is a React ref to the `Layout` element; see [React Refs](https://reactjs.org/docs/refs-and-the-dom.html)).\n\n\n## Tab Node Events\n\nYou can handle node events by adding a listener, typically within a component's `useEffect` hook:\n\nExample:\n```javascript\nfunction MyComponent({ node }) {\n  useEffect(() =\u003e {\n    const listenerId = node.setEventListener(\"save\", () =\u003e {\n      node.getConfig().subject = subject;\n    });\n    return () =\u003e node.removeEventListener(listenerId);\n  }, [subject]);\n}\n```\n\n| Event      | Parameters | Description |\n| ---------- | ---------- | ----------- |\n| resize     | `{rect}`   | Called when the tab is resized during layout, before it is rendered with the new size. |\n| close      | None       | Called when the tab is closed. |\n| visibility | `{visible}`| Called when the tab's visibility changes. |\n| save       | None       | Called before a `TabNode` is serialized to JSON. Use this to save node configuration by adding data to the object returned by `node.getConfig()`. |\n\n## Popout Windows\n\nTabs can be rendered into external browser windows (useful for multi-monitor setups) by using the `enablePopout` attribute. When enabled, a popout icon appears in the tab header.\n\nPopout windows require an additional HTML page, `popout.html`, hosted at the same location as the main page (you can copy this from the demo app). The `popout.html` acts as the host for the popped-out tab, and the main page's styles are copied into it at runtime.\n\nBecause popout windows render into a different document, any code using global `document` or `window` objects (e.g., for event listeners) will not function correctly. Instead, you must use the `document` or `window` of the popout. To obtain these, use the following methods on an element rendered within the popout (such as a ref):\n\n```javascript\nconst currentDocument = selfRef.current.ownerDocument;\nconst currentWindow = currentDocument.defaultView!;\n```\nIn this example, `selfRef` is a React ref to the top-level element in the tab being rendered.\n\nNote: Libraries may support popout windows by allowing you to specify the document to use; for example, see the `getDocument()` callback in ag-Grid at https://www.ag-grid.com/javascript-grid-callbacks/\n\n### Limitations of Popout Windows\n\nNote this section only applies to window based popouts, not floating panels.\n\n* **React Portals**: FlexLayout uses React Portals for popout content. Code runs in the main window's JS context, effectively extending the rendering area.\n* **Event Listeners**: You must use the popout's window/document when adding listeners (e.g., `popoutDocument.addEventListener(...)`).\n* **Timer Throttling**: Timers may throttle when the main window is in the background. Use web workers for high-precision timing if needed.\n* **Third-Party Libraries**: Controls that rely on the global `document` for event listeners or visibility tracking may require modification.\n* **Resize Observers**: May stay attached to the main window; alternative resize handling might be necessary.\n* **Browser Zoom**: Popouts may not size or position correctly when the browser is zoomed (e.g., at 50% zoom).\n* **States**: Popouts cannot reload in maximized or minimized states.\n* **State Preservation**: While FlexLayout maintains React state when moving tabs between windows, you can use the `enableWindowReMount` attribute to force a component to re-mount.\n\nSee this article about using React portals in this way: https://dev.to/noriste/the-challenges-of-rendering-an-openlayers-map-in-a-popup-through-react-2elh\n\n## Running the Demo and Building the Project\n\nFirst, install the dependencies:\n\n```\npnpm install\n```\n\nRun the demo app:\n\n```\npnpm dev\n```\n\nThe `pnpm dev` command watches for changes in both FlexLayout and the Demo app, allowing you to see updates in your browser immediately.\n\nOnce the demo is running, you can execute the Playwright tests in a separate terminal window:\n\n```bash\npnpm playwright\n```\n\n\u003cimg src=\"screenshots/PlaywrightUI.png?raw=true\" alt=\"PlaywrightUI\" title=\"PlaywrightUI screenshot\"/\u003e\n\nTo build the npm distribution, run `pnpm build`.\n\n## Alternative Layout Managers\n\n| Name | Repository |\n| ------------- |:-------------|\n| rc-dock | https://github.com/ticlo/rc-dock |\n| Dockview | https://dockview.dev/ |\n| lumino | https://github.com/jupyterlab/lumino |\n| golden-layout | https://github.com/golden-layout/golden-layout |\n| react-mosaic | https://github.com/nomcopter/react-mosaic |\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaplin%2Fflexlayout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaplin%2Fflexlayout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaplin%2Fflexlayout/lists"}