Sitelet https://docs.dashfoo.com/getting-started
dashfoo

Getting started

Install dashfoo and run a styled, resizable docking layout in Vite or Next.js.

Start with the default theme and a sized container. You can replace the theme or compose your own chrome once the layout is running.

Step 1: Install

In an existing React application:

npm install @dashfoo/core @dashfoo/react @dashfoo/theme
# or
pnpm add @dashfoo/core @dashfoo/react @dashfoo/theme

Starting from scratch? Copy the Vite starter or Next.js starter outside the repository, then run npm install and npm run dev (or pnpm install and pnpm dev). Both include a stateful notes panel and persistence.

The React layer supports React and React DOM 18.3.1 or 19. The Next.js starter uses React 19. Packages are ESM-only; use a modern bundler and TypeScript moduleResolution: "bundler" (or node16/nodenext). The drag, resize, and state dependencies are installed transitively; you do not configure them.

Step 2: Render a complete layout

Replace your Vite src/App.tsx with:

import { model, row, tab, tabset } from "@dashfoo/core";
import { DashfooLayout } from "@dashfoo/react";
import "@dashfoo/react/styles.css";
import "@dashfoo/theme/dashfoo.css";

const layout = model(
  row(
    [
      tabset([tab("welcome", "Welcome"), tab("notes", "Notes")], { id: "main" }),
      tabset([tab("help", "Help")], { id: "side" }),
    ],
    { id: "root" },
  ),
);

const App = () => (
  <div style={{ height: "100dvh", width: "100%" }}>
    <DashfooLayout
      defaultModel={layout}
      factory={(node) => <p>{node.name} content</p>}
      floatable
      responsive={{ maxWidth: 720 }}
    />
  </div>
);

export default App;

Replace the generated src/index.css with:

html,
body,
#root {
  height: 100%;
  margin: 0;
}

Remove the generated App.css import if it is still present. Run npm run dev or pnpm dev. You can now select, close, rename, dock, resize, maximize, and float panels. Below the responsive breakpoint, the layout stacks and disables restructuring; tabs still work.

defaultModel is read once at mount. To reset a running layout, use resetLayout() on its ref; to replace it from application state, use controlled mode.

Step 3: Render your own content

Tabs contain a component key, not a React element. A registry maps those keys to components receiving a TabNode:

import type { TabNode } from "@dashfoo/core";

const Welcome = ({ node }: { node: TabNode }) => <p>{node.name}</p>;
const Notes = () => <textarea aria-label="Notes" />;
const Help = () => <p>Drag a tab to rearrange the layout.</p>;

const components = { welcome: Welcome, notes: Notes, help: Help };

<DashfooLayout defaultModel={layout} components={components} keepMounted />;

Use either components or factory; factory wins when both are supplied. Unknown registry keys render an empty panel and warn. By default inactive panels unmount. Set keepMounted to retain local state across tab switches. This does not save widget content or guarantee state survives moving a panel to another tabset; application state that must outlive remounts belongs outside the panel.

A tab's id defaults to its component key. When reusing a component, provide unique ids: tab("notes", "Second note", { id: "notes-2" }). Builders warn on duplicates. Use stable explicit row/tabset ids for SSR and any node you address from application code.

Using Next.js?

Define the dashboard in a "use client" module: factories and component registries cannot cross the server/client boundary as props. Import the theme once in the root layout. The complete Next.js starter shows both files.

// app/dashboard.tsx
"use client";

import { model, row, tab, tabset } from "@dashfoo/core";
import { DashfooLayout } from "@dashfoo/react";

const layout = model(row([tabset([tab("welcome", "Welcome")], { id: "main" })], { id: "root" }));

export const Dashboard = () => (
  <div style={{ height: "100dvh" }}>
    <DashfooLayout defaultModel={layout} factory={(node) => <p>{node.name}</p>} />
  </div>
);

Render <Dashboard /> from your page. The persist prop renders the seed on the server and first client render, then restores saved data after hydration. See persistence for storage adapters and failure behavior.

Styling and composition

The React layer applies structural positioning and sizing styles. It does not impose a visual theme. The optional skin is light by default; set data-dashfoo-theme="dark" on an ancestor for dark mode.

For Tailwind v4, import @dashfoo/theme/tailwind.css after tailwindcss instead of the prebuilt CSS. To own the appearance, style data-dashfoo attributes and size both splitter orientations. See theming. To restructure the chrome itself, compose Layout and Tabset parts.

Troubleshooting

  • Blank layout: its parent needs a height. Use the sized wrapper above.
  • Unstyled controls or missing splitters: import the theme or supply your own skin.
  • Empty panel: check its registry key and console warnings.
  • Notes reset on tab switches: enable keepMounted.
  • Saved data is invalid or storage is blocked: dashfoo warns and keeps a usable layout. Fix the adapter or clear the saved arrangement.
  • Cannot drag on a narrow screen: responsive intentionally stacks and locks restructuring.

Docking is pointer-only. Tab selection and the exposed buttons have keyboard paths; this release does not offer keyboard docking or native browser-window popouts. Read the drag guide before choosing dashfoo for a workflow that requires keyboard rearrangement.

Concepts · Model · Floating panels · Controlled state and history · Persistence · API reference