Skip to content
MIT-licensed · headless · zero imposed styling

Docking layouts for React that you own

dashfoo ships the structure: tabs, splits, drag-dock, and a serializable model. You keep the styling, so your dashboards match your product instead of a prebuilt skin.

Everything here is live. Drag a tab to restack or split, drag a splitter to resize, double-click a tab to rename.
app.yourproduct.com

What dashfoo gives you

dashfoo gives you FlexLayout’s power without the chrome you can’t restructure. State, mutations, and drag sit behind adapters you never touch.

Headless by design
The engine renders markup tagged with data-dashfoo attributes and applies no CSS. Style the chrome to match your product.
One serializable model
The whole layout is one plain, zod-validated object: your single source of truth. Save it, diff it, restore it, or send it over the wire.
Real drag-docking
Drag a tab to restack, split, reorder, or dock to an edge. Resizable splitters and maximize come built in, pointer-driven, with no config.
Undo/redo & self-healing
Undo and redo come for free, and tree invariants self-heal after every action, so the model never drifts into an invalid state.
Controlled or not
Run it uncontrolled and let dashfoo own the state, or drive the model yourself and persist it to localStorage or your backend.
Primitives, hidden
react-resizable-panels, @dnd-kit/dom, XState v5, and zod sit behind adapters. They stay bundled and hidden, so you never import them.

Up and running in one component

Install the engine and the React layer, give the mount a height, and resolve tab content with a registry or a factory. Tab content never lives in the model. That’s the whole setup.

Read the full guide
pnpm add @dashfoo/core @dashfoo/react
import { model, row, tabset, tab } from "@dashfoo/core";
import { DashfooLayout } from "@dashfoo/react";

const layout = model(
  row([
    tabset([tab("chart", "Chart"), tab("depth", "Depth")], { weight: 2 }),
    tabset([tab("book", "Order Book")], { weight: 1 }),
  ]),
);

export const Dashboard = () => (
  <DashfooLayout
    defaultModel={layout}
    factory={(t) => <Panel node={t} />}
  />
);

Is dashfoo the right fit?

dashfoo is a split/tab tree, not a coordinate canvas. Popout windows, floating panels, and nested sub-layouts aren’t in v1.

Reach for dashfoo when…

  • You need real docking: drag tabs to stack, split, or dock to an edge.
  • You want to own every pixel of the chrome and style raw markup yourself.
  • You want the layout as a serializable model you can save, diff, and restore.
  • You want undo/redo and self-healing tree invariants for free.

Reach for something else when…

  • You need a free 2D grid with absolute x/y/w/h. Reach for react-grid-layout.
  • You want a turnkey, fully-styled panel UI out of the box.
  • A simple two-pane resizable split is all you need. Use react-resizable-panels.
  • You need panels that detach into native windows or float over the layout.

Build the dashboard your product needs

Install two packages, style the chrome yourself, and ship.