Skip to main contentflexlayout-react
FlexLayout for React - Advanced docking layout manager with drag-and-drop, tabs, splitters, and complex window management
설치로 이동 Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill flexlayout-react명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
이 저장소의 다른 Skills
LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.
| name | flexlayout-react |
| description | FlexLayout for React - Advanced docking layout manager with drag-and-drop, tabs, splitters, and complex window management |
| user-invocable | false |
| disable-model-invocation | true |
| version | 1.0.0 |
| category | development |
| author | Claude MPM Team |
| license | MIT |
| progressive_disclosure | {"entry_point":{"summary":"Professional docking layout system: drag-and-drop panels, tabs, splitters, persistence, complex multi-pane interfaces","when_to_use":"Building IDE-like interfaces, dashboard builders, multi-document editors, complex admin panels with draggable panes","quick_start":"1. Create model with Model.fromJson() 2. Wrap app in Layout component 3. Define factory function 4. Persist with model.toJson()"}} |
| context_limit | 700 |
| tags | ["react","layout","docking","drag-drop","ide","dashboard","flexlayout"] |
| requires_tools | [] |
FlexLayout-React - Professional Docking Layouts
Overview
FlexLayout-React provides IDE-quality docking layouts with drag-and-drop, tabs, splitters, and complex window management. Perfect for dashboards, IDEs, admin panels, and any interface requiring flexible, user-customizable layouts.
Key Features:
- Drag-and-drop panel repositioning
- Tabbed interfaces with close, maximize, minimize
- Splitters for resizable panes
- Border docking areas
- Layout persistence (save/restore)
- Programmatic layout control
- TypeScript support
Installation:
npm install flexlayout-react
Basic Setup
1. Define Layout Model
import { Model, IJsonModel } from 'flexlayout-react';
const initialLayout: IJsonModel = {
global: {
tabEnableClose: true,
tabEnableRename: false,
},
borders: [],
layout: {
type: 'row',
weight: 100,
children: [
{
type: 'tabset',
weight: 50,
children: [
{
type: 'tab',
name: 'Explorer',
component: 'explorer',
}
]
},
{
type: 'tabset',
: ,
: [
{
: ,
: ,
: ,
}
]
}
]
}
};
model = .(initialLayout);
weight
50
children
type
'tab'
name
'Editor'
component
'editor'
const
Model
fromJson
2. Create Layout Component
import React, { useRef } from 'react';
import { Layout, Model, TabNode, IJsonTabNode } from 'flexlayout-react';
import 'flexlayout-react/style/dark.css';
interface ComponentRegistry {
explorer: React.ComponentType;
editor: React.ComponentType;
terminal: React.ComponentType;
}
function App() {
const modelRef = useRef(Model.fromJson(initialLayout));
const factory = (node: TabNode) => {
const component = node.getComponent();
switch (component) {
case 'explorer':
return <ExplorerPanel />;
case 'editor':
return <EditorPanel />;
case 'terminal':
return <TerminalPanel />;
default:
return <div>Unknown component: {component}</div>;
}
};
return (
<div style={{ width: '100vw', height: '100vh' }}>
<Layout
model={modelRef.current}
factory={factory}
/>
</div>
);
}
3. Component Implementation
function ExplorerPanel() {
return (
<div className="panel-explorer">
<h3>File Explorer</h3>
<ul>
<li>src/</li>
<li>public/</li>
<li>package.json</li>
</ul>
</div>
);
}
function EditorPanel() {
return (
<div className="panel-editor">
<textarea
style={{ width: '100%', height: '100%' }}
placeholder="Start typing..."
/>
</div>
);
}
Advanced Layout Configurations
Complex Multi-Pane Layout
const complexLayout: IJsonModel = {
global: {
tabEnableClose: true,
tabEnableRename: false,
tabEnableDrag: true,
tabEnableFloat: true,
borderSize: 200,
},
borders: [
{
type: 'border',
location: 'left',
size: 250,
children: [
{
type: 'tab',
name: 'Explorer',
component: 'explorer',
}
]
},
{
type: 'border',
location: 'bottom',
size: 200,
children: [
{
type: 'tab',
name: 'Terminal',
component: 'terminal',
},
{
type: 'tab',
name: 'Output',
component: 'output',
}
]
}
],
layout: {
type: 'row',
weight: 100,
children: [
{
type: 'tabset',
weight: 70,
children: [
{
type: 'tab',
name: 'Editor 1',
component: 'editor',
},
{
type: 'tab',
name: 'Editor 2',
component: 'editor',
}
]
},
{
type: 'tabset',
weight: 30,
children: [
{
type: 'tab',
name: 'Properties',
component: 'properties',
},
{
type: 'tab',
name: 'Outline',
component: 'outline',
}
]
}
]
}
};
Nested Rows and Columns
const nestedLayout: IJsonModel = {
global: {},
borders: [],
layout: {
type: 'row',
children: [
{
type: 'col',
weight: 50,
children: [
{
type: 'tabset',
weight: 70,
children: [
{ type: 'tab', name: 'Top Left', component: 'panel-a' }
]
},
{
type: 'tabset',
weight: 30,
children: [
{ type: 'tab', name: 'Bottom Left', component: 'panel-b' }
]
}
]
},
{
type: 'col',
weight: 50,
children: [
{
type: 'tabset',
weight: 30,
children: [
{ type: 'tab', name: 'Top Right', component: 'panel-c' }
]
},
{
type: 'tabset',
weight: 70,
children: [
{ type: 'tab', name: 'Bottom Right', component: 'panel-d' }
]
}
]
}
]
}
};
Layout Persistence
Save and Restore Layout
import { useState, useEffect } from 'react';
import { Model, Actions } from 'flexlayout-react';
function LayoutManager() {
const [model, setModel] = useState(() => {
const saved = localStorage.getItem('layout');
return saved
? Model.fromJson(JSON.parse(saved))
: Model.fromJson(defaultLayout);
});
const onModelChange = (newModel: Model) => {
const json = newModel.toJson();
localStorage.setItem('layout', JSON.stringify(json));
};
return (
<Layout
model={model}
factory={factory}
onModelChange={onModelChange}
/>
);
}
Reset to Default Layout
function LayoutControls({ model }: { model: Model }) {
const resetLayout = () => {
const newModel = Model.fromJson(defaultLayout);
window.location.reload();
};
const saveLayout = () => {
const json = model.toJson();
const blob = new Blob([JSON.stringify(json, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'layout.json';
a.click();
};
return (
<div className="layout-controls">
<button onClick={resetLayout}>Reset Layout</button>
<button onClick={saveLayout}>Export Layout</button>
</div>
);
}
Dynamic Tab Management
Adding Tabs Programmatically
import { Actions, DockLocation } from 'flexlayout-react';
function addNewTab(model: Model, tabsetId: string) {
model.doAction(Actions.addNode(
{
type: 'tab',
name: `New Tab ${Date.now()}`,
component: 'editor',
},
tabsetId,
DockLocation.CENTER,
-1
));
}
const addToExplorer = () => {
addNewTab(model, 'explorer-tabset-id');
};
const addToActive = () => {
const activeTabset = model.getActiveTabset();
if (activeTabset) {
addNewTab(model, activeTabset.getId());
}
};
Closing Tabs
function closeTab(model: Model, tabId: string) {
model.doAction(Actions.deleteTab(tabId));
}
function closeAllTabs(model: Model) {
const tabsets = model.getRoot().getChildren();
tabsets.forEach(tabset => {
if (tabset.getType() === 'tabset') {
const tabs = tabset.getChildren();
tabs.forEach(tab => {
if (tab.getType() === 'tab') {
model.doAction(Actions.deleteTab(tab.getId()));
}
});
}
});
}
Tab Context and Props
Passing Data to Components
interface EditorTabProps {
node: TabNode;
}
function EditorTab({ node }: EditorTabProps) {
const filepath = node.getConfig()?.filepath as string;
const readonly = node.getConfig()?.readonly as boolean;
return (
<div>
<p>Editing: {filepath}</p>
<textarea readOnly={readonly} />
</div>
);
}
const factory = (node: TabNode) => {
const component = node.getComponent();
switch (component) {
case 'editor':
return <EditorTab node={node} />;
default:
return <div>Unknown</div>;
}
};
const newTab: IJsonTabNode = {
type: 'tab',
name: 'my-file.ts',
component: 'editor',
config: {
filepath: '/src/my-file.ts',
readonly: false,
}
};
Accessing Tab State
function SmartPanel({ node }: { node: TabNode }) {
const name = node.getName();
const isActive = node.isSelected();
const isVisible = node.isVisible();
return (
<div className={isActive ? 'active' : 'inactive'}>
<h3>{name}</h3>
{isVisible && <p>This tab is visible</p>}
</div>
);
}
Styling and Theming
Custom CSS
.flexlayout__layout {
background: #1e1e1e;
}
.flexlayout__tab {
background: #2d2d2d;
color: #cccccc;
}
.flexlayout__tab:hover {
background: #3e3e3e;
}
.flexlayout__tab_button--selected {
background: #1e1e1e;
border-bottom: 2px solid #007acc;
}
.flexlayout__splitter {
background: #2d2d2d;
}
.flexlayout__splitter:hover {
background: #007acc;
}
Dark/Light Theme Toggle
import 'flexlayout-react/style/dark.css';
import 'flexlayout-react/style/light.css';
function ThemeToggle() {
const [theme, setTheme] = useState<'dark' | 'light'>('dark');
useEffect(() => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `flexlayout-react/style/${theme}.css`;
document.head.appendChild(link);
return () => {
document.head.removeChild(link);
};
}, [theme]);
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
);
}
Integration with Tauri
Persisting Layout to Tauri Backend
import { invoke } from '@tauri-apps/api/core';
async function saveLayoutToTauri(model: Model) {
const json = model.toJson();
await invoke('save_layout', {
layout: JSON.stringify(json)
});
}
async function loadLayoutFromTauri(): Promise<Model> {
const layout = await invoke<string>('load_layout');
return Model.fromJson(JSON.parse(layout));
}
Window-Specific Layouts
import { invoke } from '@tauri-apps/api/core';
import { getCurrent } from '@tauri-apps/api/window';
function WindowLayout() {
const [model, setModel] = useState<Model | null>(null);
useEffect(() => {
const currentWindow = getCurrent();
const windowLabel = currentWindow.label;
invoke<string>('load_window_layout', { windowLabel })
.then(layout => {
setModel(Model.fromJson(JSON.parse(layout)));
})
.catch(() => {
setModel(Model.fromJson(defaultLayout));
});
}, []);
const onModelChange = (newModel: Model) => {
const currentWindow = getCurrent();
const json = newModel.toJson();
invoke('save_window_layout', {
windowLabel: currentWindow.label,
layout: JSON.stringify(json)
});
};
if (!model) return <div>Loading...</div>;
return (
<Layout
model={model}
factory={factory}
onModelChange={onModelChange}
/>
);
}
Advanced Patterns
Custom Tab Headers
import { Layout, Model, TabNode, ITabRenderValues } from 'flexlayout-react';
function App() {
const onRenderTab = (
node: TabNode,
renderValues: ITabRenderValues
) => {
const modified = node.getConfig()?.modified as boolean;
renderValues.content = (
<div className="custom-tab-header">
<span>{node.getName()}</span>
{modified && <span className="modified-indicator">●</span>}
</div>
);
};
return (
<Layout
model={model}
factory={factory}
onRenderTab={onRenderTab}
/>
);
}
Tab Actions (Custom Buttons)
const onRenderTab = (node: TabNode, renderValues: ITabRenderValues) => {
renderValues.buttons.push(
<button
key="save"
onClick={() => saveTabContent(node)}
title="Save"
>
💾
</button>
);
renderValues.buttons.push(
<button
key="duplicate"
onClick={() => duplicateTab(node)}
title="Duplicate"
>
📋
</button>
);
};
Best Practices
- Persist layouts - Save to localStorage or backend for user experience
- Use unique component names - Avoid collisions in factory function
- Handle missing components - Factory should have default case
- Memoize factory function - Prevent unnecessary re-renders
- Use config for tab data - Store tab-specific props in config
- Provide reset mechanism - Users can restore default layout
- Test layout changes - Verify persistence works correctly
- Handle edge cases - Empty tabsets, deleted components
- Use borders wisely - Left/right/top/bottom for tools, main area for content
- Optimize large layouts - Lazy-load components when possible
Common Pitfalls
function App() {
const model = Model.fromJson(layout);
return <Layout model={model} />;
}
function App() {
const modelRef = useRef(Model.fromJson(layout));
return <Layout model={modelRef.current} />;
}
import { Layout } from 'flexlayout-react';
❌ Not handling onModelChange:
<Layout model={model} factory={factory} />
<Layout
model={model}
factory={factory}
onModelChange={saveLayout}
/>
Resources
Summary
- FlexLayout provides IDE-quality docking layouts
- Model-driven - Define layout as JSON, control programmatically
- Persistent - Save/restore user layouts easily
- Customizable - Custom tabs, borders, themes
- React-friendly - Hooks, TypeScript support
- Perfect for - IDEs, dashboards, admin panels, complex UIs
- Tauri integration - Persist to backend, window-specific layouts
Related Skills
When using FlexLayout-React, these skills enhance your workflow:
- react-state-machine: XState v5 state machines and actor model for complex UI logic, multi-step forms, async flows
- react-hooks-composition: Advanced React hooks composition patterns for data-fetching and performance
- tanstack-query: Server-state management for React apps with caching and refetching
- zustand: Lightweight client-state management alternative to Redux
- nextjs: React framework with SSR, routing, and full-stack capabilities
- test-driven-development: TDD patterns for React components and hooks
[Full documentation available in these skills if deployed in your bundle]