Examples | API | Quick Start | Feature Tracker | Sandbox | Flexlayout
This is Flexycakes, a fork of FlexLayout with enhanced features including pin/unpin functionality for panels. Flexycakes extends the original FlexLayout capabilities with additional user interface improvements and workflow optimizations.
🚀 Exciting News! We're launching a paid bounty system to support young developers in the open source community.
What we're working on:
- 🔍 Researching the best bounty platforms
- 📋 Setting up contract logistics
- 🎯 Defining contribution opportunities
- 💡 Creating developer-friendly workflows
Stay tuned: Join our Discord community for real-time updates and early access to bounty opportunities!
Install Flexycakes from npm:
npm install flexycakes
or using yarn:
yarn add flexycakes
or using pnpm:
pnpm add flexycakes
-
Import the components:
import { Layout, Model } from "flexycakes";
-
Include a theme:
Option A: Import in your JavaScript code:
import "flexycakes/style/light.css";
Option B: Link in your HTML:
<link rel="stylesheet" href="/style/light.css" />
-
Optional: For dynamic theming, see Theme Switching
If you're contributing to Flexycakes or want to develop locally:
-
Clone the repository:
git clone https://github.com/powerdragonfire/flexycakes.git cd flexycakes
-
Install dependencies:
pnpm install
-
Create a symbolic link:
# Build the library first pnpm build # Create global link npm link # or with pnpm pnpm link --global
-
Link in your project:
cd /path/to/your/project npm link flexycakes # or with pnpm pnpm link --global flexycakes
-
Start development mode:
# In flexycakes directory pnpm dev
This will watch for changes and rebuild automatically.
The <Layout>
component renders the tabsets and splitters. Here's what you need:
Prop | Type | Description |
---|---|---|
model |
Model | The layout model containing the structure |
factory |
Function | Creates React components for each tab |
💡 Tip: See Optional Layout Props for additional configuration options.
The model is a tree of Node objects that define your layout structure:
- Created using
Model.fromJson(jsonObject)
- Saved using
model.toJson()
The factory function takes a Node object and returns the React component to render in that tab.
// Define your layout structure
const json = {
global: {},
borders: [],
layout: {
type: "row",
weight: 100,
children: [
{
type: "tabset",
weight: 50,
children: [
{
type: "tab",
name: "Dashboard",
component: "dashboard",
},
],
},
{
type: "tabset",
weight: 50,
children: [
{
type: "tab",
name: "Settings",
component: "settings",
},
],
},
],
},
};
// Create your app
const model = Model.fromJson(json);
function App() {
const factory = (node) => {
const component = node.getComponent();
switch (component) {
case "dashboard":
return <DashboardComponent />;
case "settings":
return <SettingsComponent />;
default:
return <div>{node.getName()}</div>;
}
};
return <Layout model={model} factory={factory} />;
}
🎮 Try it live: CodeSandbox Demo | TypeScript Example
The model JSON has 4 main sections:
Section | Required | Description |
---|---|---|
global |
❌ | Global layout options |
layout |
✅ | Main layout hierarchy |
borders |
❌ | Edge panels ("top", "bottom", "left", "right") |
popouts |
❌ | External window definitions |
- Purpose: Container for tabsets and child rows
- Orientation: Top-level rows are horizontal (unless
rootOrientationVertical
is set) - Children: Tabsets and other rows
- Purpose: Container for tabs
- Properties: List of tabs + selected tab index
- Behavior: Auto-created when tabs move, deleted when empty (unless
enableDeleteWhenEmpty: false
)
- Purpose: Individual panel content
- Properties: Component name (for factory) + display text
- Content: Loaded via the factory function
- Purpose: Edge-docked panels
- Location: Can only exist in the
borders
section - Behavior: Similar to tabsets but docked to screen edges
💡 Pro Tip: Use the demo app to visually create layouts, then export the JSON via 'Show Layout JSON in console'.
Node weights determine relative sizing:
- Example: Weights of 30,70 = same as 3,7
- Usage: Only relative values matter
- Application: Applies to rows and tabsets within their parent
light.css
- Clean light themedark.css
- Dark mode themegray.css
- Neutral gray themeunderline.css
- Minimal underline stylerounded.css
- Rounded corners themecombined.css
- All themes in one file
Use the combined.css
theme for runtime switching:
// Setup with theme container
<div ref={containerRef} className="flexlayout__theme_light">
<Layout model={model} factory={factory} />
</div>;
// Change theme programmatically
containerRef.current.className = "flexlayout__theme_dark";
Customize individual tabs with onRenderTab
:
const onRenderTab = (node, renderValues) => {
// renderValues.leading = <Icon />; (red area)
// renderValues.content += " *"; (green area)
renderValues.buttons.push(<MenuIcon key="menu" />); // yellow area
};
<Layout model={model} factory={factory} onRenderTab={onRenderTab} />;
Customize tabset headers with onRenderTabSet
:
const onRenderTabSet = (node, renderValues) => {
// Add persistent buttons (red area)
renderValues.stickyButtons.push(
<button key="add" onClick={() => addNewTab(node)}>
+
</button>,
);
// Add contextual buttons (green area)
renderValues.buttons.push(<SettingsIcon key="settings" />);
};
All layout changes happen through actions via Model.doAction()
:
// Add a new tab
model.doAction(
Actions.addNode(
{ type: "tab", component: "grid", name: "New Grid" },
"targetTabsetId",
DockLocation.CENTER,
0, // position (use -1 for end)
),
);
// Update global settings
model.doAction(
Actions.updateModelAttributes({
splitterSize: 40,
}),
);
📚 Reference: Full Actions API
Handle tab lifecycle events:
function MyComponent({ node }) {
useEffect(() => {
// Listen for resize events
node.setEventListener("resize", ({ rect }) => {
console.log("Tab resized:", rect);
});
// Save data before serialization
node.setEventListener("save", () => {
node.getConfig().myData = getCurrentData();
});
// Handle visibility changes
node.setEventListener("visibility", ({ visible }) => {
if (visible) startUpdates();
else stopUpdates();
});
}, [node]);
}
Event | Parameters | Description |
---|---|---|
resize |
{rect} |
Tab resized during layout |
close |
none |
Tab is being closed |
visibility |
{visible} |
Tab visibility changed |
save |
none |
Before serialization to JSON |
Render tabs in external browser windows for multi-monitor setups.
- HTML Host Page: Copy
popout.html
to your public directory - Tab Configuration: Add
enablePopout: true
to tab definitions - Styling: Styles are automatically copied from main window
Popout windows use a different document
and window
. Access them via:
function PopoutComponent() {
const selfRef = useRef();
const handleEvent = () => {
// ❌ Wrong - uses main window
document.addEventListener("click", handler);
// ✅ Correct - uses popout window
const popoutDoc = selfRef.current.ownerDocument;
const popoutWindow = popoutDoc.defaultView;
popoutDoc.addEventListener("click", handler);
};
return <div ref={selfRef}>Content</div>;
}
⚠️ Context: Code runs in main window JS context⚠️ Events: Must use popout window/document for listeners⚠️ Throttling: Timers throttle when main window is hidden⚠️ Libraries: Third-party controls may not work without modification⚠️ Zoom: Incorrect sizing when browser is zoomed⚠️ State: Windows can't reload in maximized/minimized states
📖 Deep Dive: React Portals in Popups
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Run tests (in separate terminal)
pnpm playwright
# Build for distribution
pnpm build
The built files will be in the dist/
directory.
🔧 Configuration Reference
Complete list of optional props for the <Layout>
component:
TypeScript interfaces for all configuration objects:
🎯 Advanced Usage
Programmatically add tabs using Layout component methods:
// Get reference to Layout component
const layoutRef = useRef();
// Add tab to specific tabset
layoutRef.current.addTabToTabSet("NAVIGATION", {
type: "tab",
component: "grid",
name: "New Grid",
});
Remove tabs entirely for a pure splitter interface:
const json = {
global: {
tabSetEnableTabStrip: false,
},
// ... rest of layout
};
// Auto-generated IDs are UUIDs
const autoId = "#0c459064-8dee-444e-8636-eb9ab910fb27";
// Get node ID
const nodeId = node.getId();
// Use in actions
model.doAction(Actions.selectTab(nodeId));
🔗 Alternative Solutions
Comparing Flexycakes with other React layout managers:
Library | Repository | Key Features |
---|---|---|
Flexycakes | powerdragonfire/flexycakes | Enhanced FlexLayout with pin/unpin |
rc-dock | ticlo/rc-dock | Dock-style layouts |
Dockview | dockview.dev | Modern docking solution |
Lumino | jupyterlab/lumino | JupyterLab's layout system |
Golden Layout | golden-layout/golden-layout | Multi-window layouts |
React Mosaic | nomcopter/react-mosaic | Tiling window manager |
📋 Migration Guide
Flexycakes is a drop-in replacement for FlexLayout with additional features:
-
Update package:
npm uninstall flexlayout-react npm install flexycakes
-
Update imports:
// Before import FlexLayout from "flexlayout-react"; // After import { Layout, Model } from "flexycakes";
-
Update CSS imports:
// Before import "flexlayout-react/style/light.css"; // After import "flexycakes/style/light.css";
- 📌 Pin/Unpin functionality for panels
- 🎨 Enhanced UI components
- ⚡ Improved workflow optimizations
- 🔧 Additional customization options