Dockable
Getting started

Installation

Add the core and the React primitives to a React 19 app.

Dockable is two packages: the framework-agnostic core and the React primitives over it.

pnpm add @fragiola/dockable @fragiola/dockable-react

Not on npm yet

The packages are at version 0.0.0 and are not published yet. Until they are, consume them from the repository as workspace packages ("@fragiola/dockable": "workspace:*"), which is how this site uses them.

Requirements

requirementwhy
react and react-dom ^19peer dependencies of @fragiola/dockable-react. The primitives use React 19 features: ref as a plain prop and ref cleanup functions.
A browser DOMthe engine measures elements with getBoundingClientRect and ResizeObserver. The model loads in plain Node (for tests or server-side JSON handling), but a layout renders only on the client.
ES modulesboth packages are ESM only ("type": "module").

@fragiola/dockable has zero runtime dependencies. @fragiola/dockable-react depends only on the core and its React peers.

What to import from where

// the model, the actions and the other core classes
import { Actions, DockLocation, Model, type IJsonModel } from "@fragiola/dockable";
// the primitives (one namespace) and the hooks
import { Dockable, useDockable } from "@fragiola/dockable-react";

The React package does not re-export the core. Import model classes (Model, TabNode, TabSetNode, RowNode, Actions, DockLocation, DockableLabel, …) from @fragiola/dockable.

Server rendering

The layout needs the DOM: the engine measures the elements you render and positions the panels after every commit. In a server-rendering framework (Next.js, Remix, …), mount the layout on the client only. With Next.js, mark the file "use client" and load the component with next/dynamic and ssr: false, or render it behind a mounted guard. Pre-rendering the layout would produce markup without geometry and a hydration flash.

"use client";

import dynamic from "next/dynamic";

// the layout measures the DOM: never pre-render it
const Workbench = dynamic(() => import("./workbench"), { ssr: false });

export default function Page() {
    return <Workbench />;
}

Popout windows

If you use popouts, serve a host page for the new windows (default popout.html next to your app; the path is configurable with popoutURL). It can be an empty HTML document: the core writes everything it needs into it.

Next

Your first layout builds a working layout in five steps.

On this page