Render a tiling layout in React — in one paste.
@n-uf/hypr-tiling is a controlled tiling renderer: you own a layout tree in state, and the component drags, drops, resizes, groups, and keyboard-controls its panes at runtime. Paste the block below and you have a working, resizable layout — the guides take it from there. These docs cover only the public API; contributors working on the library start from CONTRIBUTING.md.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// The golden path: a controlled TilingRenderer. You own the layout tree in
// state; the renderer reports every user edit (drag, resize, group…) through
// onLayoutChange, and you apply it straight back. renderTile paints each pane.
const tiles: TilingTile[] = [
{ id: "editor", title: "Editor" },
{ id: "preview", title: "Preview" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "editor" },
second: { kind: "leaf", id: "right", tileId: "preview" },
};
export function Quickstart(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontFamily: "system-ui", fontSize: 13 }}>
{tile.title}
</div>
)}
/>
);
}
Quickstart
Three steps to the layout above. Every step is runnable; there are no concept detours here — those come after, only as needed.
Install
Add the package and its React 19 peers.
pnpm add @n-uf/hypr-tiling react react-domAdd the Tailwind content glob
The library ships utility classes in its dist, not CSS. Add that directory to your Tailwind content globs or the pane, divider, and drag-ghost classes get purged.
// tailwind.config.{js,ts}
export default {
content: [
"./src/**/*.{ts,tsx}",
// Required: hypr-tiling ships utility classes in its dist output.
// Without this glob Tailwind purges the pane / divider / ghost classes.
"./node_modules/@n-uf/hypr-tiling/dist/**/*.{js,mjs,cjs}",
],
};Render a controlled TilingRenderer
Own the layout tree in state, pass a config, and apply every edit the renderer reports through onLayoutChange. This is the exact source behind the live panes above — drag a header to rearrange, drag the divider to resize.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// The golden path: a controlled TilingRenderer. You own the layout tree in
// state; the renderer reports every user edit (drag, resize, group…) through
// onLayoutChange, and you apply it straight back. renderTile paints each pane.
const tiles: TilingTile[] = [
{ id: "editor", title: "Editor" },
{ id: "preview", title: "Preview" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "editor" },
second: { kind: "leaf", id: "right", tileId: "preview" },
};
export function Quickstart(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontFamily: "system-ui", fontSize: 13 }}>
{tile.title}
</div>
)}
/>
);
}
Next: paint your own pane content in Render your own content, then skim Concepts when you want the mental model.
How do I…
Each recipe is an outcome you want, a complete snippet that compiles against the current public API, the two or three knobs that matter, and where to look next.
Define the initial layout
Describe the starting arrangement as a plain, serialisable tree you own in state — a leaf holds one tile, a split divides space by a ratio, a group stacks leaves behind a tab strip.
import type { TilingLayoutNode, TilingTile } from "@n-uf/hypr-tiling";
// A layout is a plain, serialisable tree you own in state. Three node kinds:
// • leaf — holds one tile (by tileId)
// • split — divides space along an axis (horizontal | vertical) by a ratio
// • group — stacks several leaves behind a tab strip (one active at a time)
// Every node needs a stable `id`. A leaf's `tileId` points into your tiles.
export const tiles: TilingTile[] = [
{ id: "files", title: "Files" },
{ id: "editor", title: "Editor" },
{ id: "terminal", title: "Terminal" },
{ id: "problems", title: "Problems" },
];
// Files on the left; a stacked editor over a tabbed group (terminal + problems)
// on the right. This whole object is JSON-serialisable — persist and restore it.
export const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.25,
first: { kind: "leaf", id: "files-pane", tileId: "files" },
second: {
kind: "split",
id: "main",
axis: "vertical",
ratio: 0.7,
first: { kind: "leaf", id: "editor-pane", tileId: "editor" },
second: {
kind: "group",
id: "bottom-group",
activeMemberId: "terminal-pane",
members: [
{ kind: "leaf", id: "terminal-pane", tileId: "terminal" },
{ kind: "leaf", id: "problems-pane", tileId: "problems" },
],
},
},
};
Every node needs a stable id; a leaf’s tileId points into your tiles. Splits carry an axis ("horizontal" / "vertical") and a ratio in [0,1]; groups carry an activeMemberId. The whole object is JSON — persist and restore it verbatim.
Render your own content in a pane
Take over what each pane draws with a renderTile callback while the renderer keeps owning layout, resize, and drag.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// renderTile paints the BODY and chrome of every pane — it's how you render your
// own content. The renderer still owns layout, splits, resize and drag; your job
// is to draw one pane and forward its interaction handles:
// • root must be `article[data-leaf-id]` (the renderer resolves the drag source
// from it) and wire onFocus / onPointerMove / onPointerLeave
// • the header wires onHandlePointerDown (this is the drag-pickup handle)
// Everything else in TilingRenderTileProps (isFocused, isMaximized, the tile
// payload, …) is presentation state you style from.
const tiles: TilingTile[] = [
{ id: "chart", title: "Revenue", content: <strong>$1.2M</strong> },
{ id: "log", title: "Activity", content: <span>3 new events</span> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "a", tileId: "chart" },
second: { kind: "leaf", id: "b", tileId: "log" },
};
function Pane(args: TilingRenderTileProps): ReactElement {
return (
<article
data-leaf-id={args.leafId}
tabIndex={-1}
onFocus={args.onFocus}
onPointerMove={args.onPointerMove}
onPointerLeave={args.onPointerLeave}
style={{
display: "flex",
flexDirection: "column",
height: "100%",
border: args.isFocused ? "1px solid #fbbf24" : "1px solid #ffffff1a",
borderRadius: 8,
background: "#121316",
overflow: "hidden",
}}
>
<header
onPointerDown={args.onHandlePointerDown}
style={{ cursor: "grab", padding: "6px 10px", fontSize: 11, color: "#d6d3d1" }}
>
{args.tile.title}
</header>
<div style={{ padding: 10, fontSize: 13, color: "#e7e5e4" }}>
{args.tile.content}
</div>
</article>
);
}
export function RenderTileExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={(args: TilingRenderTileProps): ReactNode => <Pane {...args} />}
/>
);
}
Root the pane on article[data-leaf-id] and forward onFocus, onPointerMove, and onPointerLeave; put onHandlePointerDown on your drag handle (the header). Everything else on TilingRenderTileProps (isFocused, isMaximized, the tile payload) is presentation state you style from. renderTile is a full-pane render prop, not a content slot — to draw your own header, buttons, and frame, see Render your own pane frame & header.
Render your own pane frame & header (full custom look-and-feel)
Own the ENTIRE pane, not just its body: draw your own frame, header, and controls, and wire them to the renderer’s drag, maximize, focus, and grouping handlers. Same generic renderTile prop as above — it returns the whole pane and hands you every interaction handle and state flag, so no showcase-only prop is involved.
import { useState, type CSSProperties, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
useTilingTheme,
isMultiSelectModifierActive,
TilingPaneRoot,
TilingDragHandle,
TilingPaneAction,
TilingPaneBody,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
type TilingTheme,
} from "@n-uf/hypr-tiling";
// renderTile is a FULL-PANE render prop, not a content slot: it returns the
// WHOLE pane — frame, header, body — and receives every interaction handle +
// state flag the renderer computes. That means you can own the ENTIRE pane
// look-and-feel (your own frame, header, buttons, focus/drag affordances), not
// just `tile.content`, while the renderer keeps running layout, resize, drag,
// grouping, focus and keyboard control underneath.
//
// There are two ways to write a custom pane, shown side by side below:
//
// • THE EASY PATH — the optional helper primitives (TilingPaneRoot,
// TilingDragHandle, TilingPaneAction, TilingPaneBody) encode the wiring
// rules for you, so you only bring styling + content.
// • THE RAW PATH — plain DOM wired by hand, the full escape hatch, for when
// you need control the primitives don't give.
//
// The contract every custom pane must honor (the primitives encode all of it):
// • root is `article[data-leaf-id={leafId}]` — the renderer resolves the drag
// source from it (`closest("article[data-leaf-id]")`); wire onFocus,
// onPointerMove and onPointerLeave on it too.
// • your drag handle (typically the header) wires onHandlePointerDown with
// `touch-action: none`.
// • header action buttons stopPropagation on pointerDown + click so they don't
// start a drag or steal focus.
// • render the body only when paneBodyRenderMode === "render-content" (the
// drag ghost reuses the same render path, so this keeps the ghost body in
// sync and never empty).
const tiles: TilingTile[] = [
{ id: "editor", title: "editor", accent: "sky", content: <code>main.tsx</code> },
{ id: "preview", title: "preview", accent: "emerald", content: <span>rendered output</span> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "a", tileId: "editor" },
second: { kind: "leaf", id: "b", tileId: "preview" },
};
// THE EASY PATH — the same custom pane, built with the helper primitives. Each
// primitive owns one wiring rule; you supply only className/style and content.
function PrimitivesPane(args: TilingRenderTileProps): ReactElement {
const theme: TilingTheme = useTilingTheme();
return (
<TilingPaneRoot
pane={args}
className={args.isFocused ? theme.resolveFocusFrame(args.tile.accent) : ""}
style={{
display: "flex",
flexDirection: "column",
height: "100%",
borderRadius: 10,
background: "#101114",
overflow: "hidden",
opacity: args.isDragSource ? 0.6 : 1,
boxShadow: "0 18px 40px -30px rgba(0,0,0,0.9)",
}}
>
<TilingDragHandle
pane={args}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
padding: "7px 10px",
cursor: "grab",
borderBottom: "1px solid #ffffff14",
}}
>
<span
className={theme.resolveAccentText(args.tile.accent)}
style={{ fontFamily: "ui-monospace, monospace", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.14em" }}
>
{args.tile.title}
</span>
<span style={{ display: "flex", gap: 6 }}>
{args.isMultiSelected && args.canGroupMultiSelection ? (
<TilingPaneAction
onClick={(): void => args.onGroupMultiSelection(args.leafId)}
style={{ fontSize: 10, padding: "2px 6px", borderRadius: 6, border: "1px solid #fbbf2470", color: "#fde68a", background: "transparent" }}
>
group
</TilingPaneAction>
) : null}
{args.isMaximizeEnabled ? (
<TilingPaneAction
onClick={(): void => args.onToggleMaximize()}
aria-label={args.isMaximized ? "restore pane" : "maximize pane"}
style={{ fontSize: 12, lineHeight: 1, padding: "2px 6px", borderRadius: 6, border: "1px solid #ffffff1f", color: "#d6d3d1", background: "transparent" }}
>
{args.isMaximized ? "\u2715" : "\u2922"}
</TilingPaneAction>
) : null}
</span>
</TilingDragHandle>
<TilingPaneBody pane={args} style={{ flex: 1, minHeight: 0, overflow: "auto", padding: 12, fontSize: 13, color: "#e7e5e4" }}>
{args.tile.content}
</TilingPaneBody>
</TilingPaneRoot>
);
}
// THE RAW PATH — the identical pane wired by hand: article[data-leaf-id],
// header onPointerDown, buttons that stopPropagation, body render-mode gate.
// Only the public TilingRenderTileProps handles + flags and theme tokens are
// used — no debug/observability fields exist on the contract.
function RawPane(args: TilingRenderTileProps): ReactElement {
const theme: TilingTheme = useTilingTheme();
const frameStyle: CSSProperties = {
display: "flex",
flexDirection: "column",
height: "100%",
borderRadius: 10,
background: "#101114",
overflow: "hidden",
outline: "none",
opacity: args.isDragSource ? 0.6 : 1,
boxShadow: "0 18px 40px -30px rgba(0,0,0,0.9)",
};
return (
<article
data-leaf-id={args.leafId}
tabIndex={-1}
onFocus={args.onFocus}
onPointerMove={args.onPointerMove}
onPointerLeave={args.onPointerLeave}
style={frameStyle}
// Focus frame comes from the active theme + this pane's accent — a custom
// frame still reads as part of the themed surface.
className={args.isFocused ? theme.resolveFocusFrame(args.tile.accent) : ""}
>
<header
onPointerDown={args.onHandlePointerDown}
onClick={(event): void => {
// Alt/Opt+click toggles this pane's multi-selection without focusing.
if (args.isMultiSelectGroupingEnabled && isMultiSelectModifierActive(event)) {
event.stopPropagation();
event.preventDefault();
args.onToggleMultiSelect();
}
}}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
padding: "7px 10px",
cursor: "grab",
touchAction: "none",
borderBottom: "1px solid #ffffff14",
}}
>
<span
className={theme.resolveAccentText(args.tile.accent)}
style={{ fontFamily: "ui-monospace, monospace", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.14em" }}
>
{args.tile.title}
</span>
<span style={{ display: "flex", gap: 6 }}>
{args.isMultiSelected && args.canGroupMultiSelection ? (
<button
type="button"
onPointerDown={(e): void => e.stopPropagation()}
onClick={(e): void => {
e.stopPropagation();
args.onGroupMultiSelection(args.leafId);
}}
style={{ fontSize: 10, padding: "2px 6px", borderRadius: 6, border: "1px solid #fbbf2470", color: "#fde68a", background: "transparent" }}
>
group
</button>
) : null}
{args.isMaximizeEnabled ? (
<button
type="button"
onPointerDown={(e): void => e.stopPropagation()}
onClick={(e): void => {
e.stopPropagation();
args.onToggleMaximize();
}}
aria-label={args.isMaximized ? "restore pane" : "maximize pane"}
style={{ fontSize: 12, lineHeight: 1, padding: "2px 6px", borderRadius: 6, border: "1px solid #ffffff1f", color: "#d6d3d1", background: "transparent" }}
>
{args.isMaximized ? "\u2715" : "\u2922"}
</button>
) : null}
</span>
</header>
<div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: 12, fontSize: 13, color: "#e7e5e4" }}>
{args.paneBodyRenderMode === "render-content" ? args.tile.content : null}
</div>
</article>
);
}
export function CustomChromeExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="mosaic"
onLayoutChange={setLayout}
// The left pane uses the helper primitives (easy path); the right pane is
// wired by hand (raw path). Both are fully interactive — drag, resize,
// focus, maximize, group.
renderTile={(args: TilingRenderTileProps): ReactNode =>
args.leafId === "a" ? <PrimitivesPane {...args} /> : <RawPane {...args} />
}
/>
);
}
Easiest path: the optional helper primitives. TilingPaneRoot spreads data-leaf-id + the focus/hover handlers, TilingDragHandle wires the drag pickup with touch-action: none and the Alt/Opt+click group toggle, TilingPaneAction is a header button that stops propagation so it never starts a drag or steals focus, and TilingPaneBody renders its children only in "render-content" mode. They encode the wiring rules so a custom pane can’t get them wrong; the raw renderTile args remain the full escape hatch. By hand: root on article[data-leaf-id] (the renderer resolves the drag source from it) and forward onFocus, onPointerMove, onPointerLeave. Put onHandlePointerDown on your header, expose onToggleMaximize, and add Alt/Opt+click grouping via onToggleMultiSelect + onGroupMultiSelection (gate with isMultiSelectModifierActive). Style from the state flags (isFocused, isMaximized, isDragSource) and compose with theme tokens from useTilingTheme() (resolveAccentText, resolveFocusFrame). Render the body only when paneBodyRenderMode is "render-content" so the drag ghost mirrors your pane. The homepage’s own tiles are built exactly this way — and the “panes” switch on the homepage flips them to a light minimalist variant built with these primitives.
Theme & color panes
Restyle the whole surface with a built-in theme, or give an individual pane its own accent — switching is live, with no remount.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
TilingThemeProvider,
useTilingTheme,
resolveTilingTheme,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingThemeId,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Two ways to theme:
// 1. Pick a built-in theme with the `themeId` prop ("neon-terminal" |
// "clean-flat" | "mosaic"). Switching is live — no remount.
// 2. Give a pane its own per-tile accent via `tile.accent` (one of eight hues).
// Inside a pane you can read the active theme with useTilingTheme() — wrap a
// subtree in TilingThemeProvider to supply it (resolveTilingTheme maps an id to
// the token object).
const tiles: TilingTile[] = [
{ id: "a", title: "Signals", accent: "emerald" },
{ id: "b", title: "Alerts", accent: "rose" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
// A pane body that reads the live theme id from context.
function ThemedBody({ title }: { title: string }): ReactElement {
const theme = useTilingTheme();
return (
<div style={{ padding: 12, fontSize: 13 }}>
{title} · theme: {theme.id}
</div>
);
}
export function ThemingExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const [themeId, setThemeId] = useState<TilingThemeId>("neon-terminal");
return (
<TilingThemeProvider theme={resolveTilingTheme(themeId)}>
<button type="button" onClick={(): void => setThemeId("clean-flat")}>
Use clean-flat
</button>
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId={themeId}
onLayoutChange={setLayout}
onThemeChange={setThemeId}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<ThemedBody title={tile.title} />
)}
/>
</TilingThemeProvider>
);
}
Set themeId ("neon-terminal" / "clean-flat" / "mosaic") for the whole renderer; set tile.accent for one pane. useTilingTheme() reads the active theme inside a pane; resolveTilingTheme maps an id to its token object for TilingThemeProvider.
Choose which interactions are allowed
Turn interactions on or off — everything is enabled by default, so you subtract what you don’t want through one prop.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
TILING_DASHBOARD_PRESET,
resolveInteractionCapabilities,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingInteractionCapabilities,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Every interaction (drag, resize, keyboard, grouping, maximize) is ON by
// default. Narrow behavior through the single `interaction` prop: pass a partial
// TilingInteractionCapabilities, or start from a preset and override. Here we
// take TILING_DASHBOARD_PRESET but disable grouping and lock resizing to the
// horizontal axis. resolveInteractionCapabilities() materializes the fully
// resolved shape if you need to read effective values yourself.
const interaction: TilingInteractionCapabilities = {
...TILING_DASHBOARD_PRESET,
grouping: false,
resize: "horizontal",
};
// Effective, fully-defaulted capabilities — handy for driving your own UI.
const resolved = resolveInteractionCapabilities(interaction);
export const isGroupingEnabled: boolean = resolved.grouping.enable;
const tiles: TilingTile[] = [
{ id: "a", title: "Board" },
{ id: "b", title: "Detail" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.6,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
export function CapabilitiesExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
interaction={interaction}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
);
}
Pass a partial TilingInteractionCapabilities to the interaction prop, or spread a preset like TILING_DASHBOARD_PRESET and override. resolveInteractionCapabilities returns the fully-defaulted shape when you need to read effective values. Every interaction is on by default; the lone opt-in is the tab strip’s dev-only content toggle (paneSwitching.showContentToggle), off unless you ask for it.
Save & restore a layout
Persist the arrangement across reloads. Because the tree is plain JSON you own, this is just save on change and load on mount.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Because YOU own the tree and it's plain JSON, persistence is just save/load.
// onLayoutChange fires on every user edit — write it to storage there. On mount,
// read it back (falling back to a default). No library-specific serializer.
const STORAGE_KEY = "my-app.layout";
const tiles: TilingTile[] = [
{ id: "a", title: "Nav" },
{ id: "b", title: "Content" },
];
const defaultLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.3,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
function loadLayout(): TilingLayoutNode {
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY) : null;
if (raw == null) {
return defaultLayout;
}
// In real code, validate the parsed shape before trusting it.
return JSON.parse(raw) as TilingLayoutNode;
}
export function SaveRestoreExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(loadLayout);
const persist = (next: TilingLayoutNode): void => {
setLayout(next);
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
};
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={persist}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
);
}
Write the layout to storage inside onLayoutChange and read it back in your useState initializer with a default fallback. Validate the parsed shape before trusting it in production. No library-specific serializer is involved.
Trigger actions from your own buttons
Drive the layout from a toolbar, menu, or any control you build — split, group, resize, maximize — without touching the tree by hand.
import { useRef, useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingCommandHandle,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Drive the layout from YOUR OWN buttons. Take the renderer's imperative handle
// with a ref, then dispatch typed TilingCommands — the same command set the
// keyboard and drag layers use. Great for toolbars, context menus, or a
// "reset layout" button. A command targeting a disabled capability is a safe
// no-op, so you never have to guard the happy path.
const tiles: TilingTile[] = [
{ id: "a", title: "Left" },
{ id: "b", title: "Right" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "a" },
second: { kind: "leaf", id: "right", tileId: "b" },
};
export function CommandsExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const handle = useRef<TilingCommandHandle>(null);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%" }}>
<div style={{ display: "flex", gap: 6 }}>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "set-split-ratio", splitId: "root", ratio: 0.7 })
}
>
Widen left
</button>
<button
type="button"
onClick={(): void => handle.current?.dispatch({ kind: "toggle-split-axis", splitId: "root" })}
>
Flip axis
</button>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "group-leaves", leafIds: ["left", "right"] })
}
>
Group both
</button>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Take the renderer’s TilingCommandHandle with a ref and call dispatch with a typed TilingCommand (set-split-ratio, toggle-split-axis, group-leaves, …). A command targeting a disabled capability is a safe no-op, so you never have to guard the happy path.
Build a command bar / keyboard shortcuts
advancedWire your own command bar or key bindings that only surface — and only fire — when the target command would actually do something.
import { useRef, useState, type KeyboardEvent, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
resolveInteractionCapabilities,
isCommandEnabled,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingCommand,
type TilingCommandGates,
type TilingCommandHandle,
type TilingInteractionCapabilities,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// ADVANCED: build your own command bar / keyboard shortcuts. isCommandEnabled is
// the gate the renderer uses for its own shortcut chips — pass it a TilingCommand
// plus the gates derived from your resolved capabilities and it tells you whether
// the command would do anything. Use it to hide dead buttons and to keep a
// keyboard binding browser-graceful (only preventDefault when the command runs).
// Map resolved capabilities → the gate flags isCommandEnabled reads.
function gatesFor(interaction?: TilingInteractionCapabilities): TilingCommandGates {
const caps = resolveInteractionCapabilities(interaction);
return {
maximizeEnabled: caps.maximize.enable,
paneSwitchingEnabled: caps.paneSwitching.enable,
focusEnabled: caps.focus,
rearrangeEnabled: caps.rearrange,
sizingEnabled: caps.paneTitleBarControls.sizing,
acquireSpaceEnabled: caps.paneTitleBarControls.acquireSpace,
resizeEnabled: caps.resize !== "none",
layoutEnabled: caps.masterLayout,
groupingEnabled: caps.grouping.enable,
};
}
interface BarEntry {
readonly label: string;
readonly key: string;
readonly command: TilingCommand;
}
const ENTRIES: ReadonlyArray<BarEntry> = [
{ label: "Next pane", key: "]", command: { kind: "focus-cycle", direction: "next" } },
{ label: "Maximize", key: "m", command: { kind: "toggle-maximize" } },
{ label: "Master layout", key: "l", command: { kind: "cycle-layout-mode" } },
];
const tiles: TilingTile[] = [
{ id: "a", title: "One" },
{ id: "b", title: "Two" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
export function CommandBarExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const handle = useRef<TilingCommandHandle>(null);
const gates: TilingCommandGates = gatesFor();
// Only surface commands that are actually enabled right now.
const available: ReadonlyArray<BarEntry> = ENTRIES.filter((entry): boolean =>
isCommandEnabled(entry.command, gates),
);
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
const match = available.find((entry): boolean => entry.key === event.key);
if (match != null) {
event.preventDefault();
handle.current?.dispatch(match.command);
}
};
return (
<div
onKeyDown={onKeyDown}
tabIndex={0}
style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%", outline: "none" }}
>
<div style={{ display: "flex", gap: 6 }}>
{available.map((entry): ReactElement => (
<button
key={entry.label}
type="button"
title={`shortcut: ${entry.key}`}
onClick={(): void => handle.current?.dispatch(entry.command)}
>
{entry.label}
</button>
))}
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Derive TilingCommandGates from resolveInteractionCapabilities, then call isCommandEnabled(command, gates) to hide dead controls and keep a keyboard binding browser-graceful (only preventDefault when the command runs). This is the same gate the renderer uses for its built-in shortcut chips.
Group, split & maximize panes
Fold panes into a tabbed group, split space, or maximize a pane — as built-in gestures and from your own code.
import { useRef, useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingCommandHandle,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Grouping, splitting and maximizing are built-in interactions (Alt/Opt+G to
// group a selection, Alt+Enter to maximize, drag a header to split), but you can
// also drive them from code with commands:
// • group-leaves — fold leaves into one tabbed group
// • insert-adjacent — split: drop one leaf beside another
// • toggle-maximize — maximize / restore a leaf
const tiles: TilingTile[] = [
{ id: "a", title: "A" },
{ id: "b", title: "B" },
{ id: "c", title: "C" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.34,
first: { kind: "leaf", id: "a", tileId: "a" },
second: {
kind: "split",
id: "rest",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "b", tileId: "b" },
second: { kind: "leaf", id: "c", tileId: "c" },
},
};
export function GroupSplitMaximizeExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const [maximizedLeafId, setMaximizedLeafId] = useState<string | null>(null);
const handle = useRef<TilingCommandHandle>(null);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%" }}>
<div style={{ display: "flex", gap: 6 }}>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "group-leaves", leafIds: ["b", "c"] })
}
>
Group B + C
</button>
<button
type="button"
onClick={(): void => handle.current?.dispatch({ kind: "toggle-maximize", leafId: "a" })}
>
Maximize A
</button>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
maximizedLeafId={maximizedLeafId}
onMaximizedLeafChange={setMaximizedLeafId}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Users can Alt/Opt+G to group, drag a header to split, and Alt+Enter to maximize out of the box. From code, dispatch group-leaves, insert-adjacent, and toggle-maximize. For custom multi-select affordances, isMultiSelectModifierActive reports whether the platform modifier is held.
Concepts
Three ideas unblock everything above. That is deliberately all — there is no architecture here.
leaf (one tile), split (two children divided by a ratio along an axis), and group (leaves stacked behind a tab strip). It is plain, serialisable data held in your state.TilingRenderer is controlled. It performs drag, resize, grouping, focus, and keyboard control, then reports the resulting tree through onLayoutChange — it never mutates state behind your back. You apply the edit (or persist / diff / veto it).interaction prop (TilingInteractionCapabilities) subtracts or reshapes behavior; presets are just pre-filled partials. You configure by turning things off, not wiring things on — the one opt-in is the dev-only pane-content toggle, off by default.Examples gallery
Complete, controlled TilingRenderer apps. Each file is runnable as-is and type-checked against the public API — copy one and start editing. Neither passes renderTile, so each uses the renderer’s default pane chrome and is fully interactive: drag a header, drag a divider, Alt/Opt-click headers then group.
Metrics dashboard
A master-stack of accented metric panes on the clean-flat theme.
import { useState, type ReactElement } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
} from "@n-uf/hypr-tiling";
// A whole runnable dashboard: four metric/detail panes in a master-stack shape,
// each with its own accent and content. Copy this file wholesale — it's a
// complete, controlled TilingRenderer app.
//
// No `renderTile` is passed, so the renderer supplies its DEFAULT pane chrome —
// a themed frame + header that is fully interactive out of the box: drag a header
// to rearrange, drag a divider to resize, Alt/Opt+click headers then "Group" to
// tab them, and maximize. Each pane paints its `tile.content` in the body (the
// dev-only content toggle is off by the library default, so content shows at
// rest). Reach for a `renderTile` callback only when you want to own the pane
// frame yourself — see the "custom look-and-feel" guide.
interface Metric {
readonly label: string;
readonly value: string;
}
function MetricCard({ metric }: { metric: Metric }): ReactElement {
return (
<div style={{ padding: 14, fontFamily: "system-ui" }}>
<div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: "#a8a29e" }}>
{metric.label}
</div>
<div style={{ fontSize: 26, fontWeight: 600, color: "#fafaf9" }}>{metric.value}</div>
</div>
);
}
const tiles: TilingTile[] = [
{ id: "revenue", title: "Revenue", accent: "emerald", content: <MetricCard metric={{ label: "MRR", value: "$48.2k" }} /> },
{ id: "users", title: "Active users", accent: "sky", content: <MetricCard metric={{ label: "DAU", value: "12,904" }} /> },
{ id: "errors", title: "Error rate", accent: "rose", content: <MetricCard metric={{ label: "5xx / min", value: "0.03%" }} /> },
{ id: "latency", title: "Latency", accent: "amber", content: <MetricCard metric={{ label: "p95", value: "142 ms" }} /> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: {
kind: "split",
id: "left",
axis: "vertical",
ratio: 0.5,
first: { kind: "leaf", id: "revenue-pane", tileId: "revenue" },
second: { kind: "leaf", id: "users-pane", tileId: "users" },
},
second: {
kind: "split",
id: "right",
axis: "vertical",
ratio: 0.5,
first: { kind: "leaf", id: "errors-pane", tileId: "errors" },
second: { kind: "leaf", id: "latency-pane", tileId: "latency" },
},
};
export function DashboardApp(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="clean-flat"
onLayoutChange={setLayout}
/>
);
}
Terminal grid
Monospace shell / logs / htop panes on the neon-terminal theme — the Hyprland homage, in the terminal.
$ pnpm dev ▸ ready in 312ms $ _
[info] listening :3000 [info] compiled ok
cpu 12% mem 3.1G / 16G load 0.42
import { useState, type ReactElement } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
} from "@n-uf/hypr-tiling";
// A whole runnable "terminal grid" — the Hyprland homage made literal: several
// monospace shell panes a user splits, stacks and rearranges at runtime. Copy
// this file wholesale. No `renderTile` is passed, so the renderer's default pane
// chrome (fully interactive: drag / resize / group / maximize) frames each
// terminal and paints its `tile.content` body at rest.
function Terminal({ lines }: { lines: ReadonlyArray<string> }): ReactElement {
return (
<pre
style={{
margin: 0,
padding: 12,
height: "100%",
overflow: "auto",
fontFamily: "ui-monospace, monospace",
fontSize: 12,
lineHeight: 1.6,
color: "#a7f3d0",
background: "#0a0b0d",
}}
>
{lines.join("\n")}
</pre>
);
}
const tiles: TilingTile[] = [
{ id: "shell", title: "zsh", content: <Terminal lines={["$ pnpm dev", "▸ ready in 312ms", "$ _"]} /> },
{ id: "logs", title: "logs", content: <Terminal lines={["[info] listening :3000", "[info] compiled ok"]} /> },
{ id: "top", title: "htop", content: <Terminal lines={["cpu 12%", "mem 3.1G / 16G", "load 0.42"]} /> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "vertical",
ratio: 0.6,
first: {
kind: "split",
id: "top-row",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "shell-pane", tileId: "shell" },
second: { kind: "leaf", id: "logs-pane", tileId: "logs" },
},
second: { kind: "leaf", id: "top-pane", tileId: "top" },
};
export function TerminalGridApp(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="neon-terminal"
onLayoutChange={setLayout}
/>
);
}
API reference
The generated per-symbol reference for the curated public API surface, produced from the library’s source TSDoc via API Extractor and API Documenter. This is a fallback — reach for it once you already know a symbol name; the guides above are the way in. Internal and devtools-only symbols are excluded, so every entry is part of the supported consumer contract, grouped by category and browsable from the sidebar tree. The full machine-readable report lives in the API report.
Renderer & tiles
The TilingRenderer component, its props, and the tile + custom render-prop contract.
TilingRenderer
variableThe controlled tiling renderer — the single component a consumer mounts. Its public prop surface (TilingRendererProps) is the curated, debug-free contract. The underlying component also accepts the devtools-tier TilingRendererObservabilityProps; that widened view is exported as TilingRenderer from @n-uf/hypr-tiling/devtools for the observability panel.
Signature:
TilingRenderer: React.ForwardRefExoticComponent<TilingRendererProps & React.RefAttributes<TilingCommandHandle>>
TilingRendererProps
interfaceProps for the TilingRenderer component — the full controlled-component surface. layout + tiles + config + onLayoutChange are the four required props; everything else is optional and resolves to a documented default.
Signature:
interface TilingRendererProps
Remarks
The renderer is a CONTROLLED component: you hold the layout tree in state and apply every edit it reports through onLayoutChange. Focus, maximize, theme, and accent can each be left uncontrolled (renderer-managed) or lifted into your state via the matching on*Change callback. Interaction behavior is tuned entirely through the single interaction prop (TilingInteractionCapabilities). This surface is debug-free: the drag/drop observability overlays, hit-zone visualizations, and telemetry feeds are a devtools concern, exposed through TilingRendererObservabilityProps on the @n-uf/hypr-tiling/devtools entry rather than here.
Example
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
/>
);
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | (Optional) Optional class name applied to the tiling root element. | ||
Global geometry configuration (gap, min pane size, handle size). | |||
boolean | (Optional) Master gate for all drag-motion choreography. When | ||
string | (Optional) CSS | ||
string | (Optional) CSS | ||
string | null | (Optional) Controlled focused-leaf id. | ||
number | (Optional) Speed of the dragged GHOST's transit animation (hop-in / hop-out / pickup entrance) as a percent of the 170ms baseline ( | ||
(Optional) Reactive interaction-capability flags. Undefined resolves to all-enabled. Changing this prop at runtime updates renderer behavior immediately. | |||
The controlled layout tree to render. Apply every reported edit back here. | |||
string | null | (Optional) Controlled maximized-pane id. | ||
(leafId: string) => void | (Optional) Notified whenever the focused leaf changes. | ||
(layout: TilingLayoutNode) => void | Called with the next layout tree whenever the renderer edits it (controlled). | ||
(leafId: string | null) => void | (Optional) Notified whenever the maximized pane changes ( | ||
(themeId: TilingThemeId) => void | (Optional) Notified when the in-renderer theme switcher (top-bar control) requests a different theme. Present this control only when wired; the consumer owns the | ||
(tileId: string, accent: TilingTileAccent) => void | (Optional) When provided, the top-bar tab strip surfaces an accent palette picker (the enumerable | ||
number | (Optional) Background opacity | ||
(args: TilingRenderTileProps) => React.ReactNode | (Optional) Custom pane renderer. Receives TilingRenderTileProps; omit to use the default tile. | ||
boolean | (Optional) Whether drop border hints are painted on candidate panes during a drag. | ||
boolean | (Optional) Whether translucent projected landing overlays are shown during a drag. | ||
number | (Optional) Speed of the affected ("survivor") panes' REFLOW transform animation as a percent of the 170ms baseline ( | ||
number | (Optional) Swap-landing bounce magnitude as a percent of full overshoot ( | ||
(Optional) Full consumer-authored theme; takes precedence over | |||
(Optional) Active visual theme id. Selects which built-in | |||
ReadonlyArray<TilingTile> | ReadonlyMap<string, TilingTile> | Tile registry, accepted as either an ordered array (resolved by |
TilingRenderTileProps
interfaceThe argument object passed to a custom renderTile. This is the clean, consumer-facing pane contract: the tile payload, this pane's derived state (focus / drag / drop / sizing), and the imperative callbacks a custom pane surface wires to its header, drag handle, and controls. Every field is something a custom pane legitimately reads to style itself or wires to drive an interaction — nothing here is a debug, observability, or showcase concern (those live on the internal render path and the /devtools surface).
Signature:
interface TilingRenderTileProps
Remarks
A minimal custom pane needs only three wiring rules, each of which the optional public helper primitives (TilingPaneRoot(), TilingDragHandle(), TilingPaneAction(), TilingPaneBody()) encode so they cannot be gotten wrong: - the pane root must carry data-leaf-id={leafId} (the renderer resolves the drag source through the [data-leaf-id] attribute) and wire onFocus, onPointerMove, onPointerLeave; - the drag handle wires onHandlePointerDown with touch-action: none; - header action buttons must stopPropagation on pointer-down + click so a click does not start a drag or steal focus; - the body renders only when paneBodyRenderMode === "render-content" (the drag ghost reuses the same render path).
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the current multi-selection ( | ||
TilingLeafDropZone | null | The resolved drop zone under the cursor for this pane, or | ||
| TilingRenderTileGroupContext | null | Group context when this pane is a tabbed group's ACTIVE member (the only member that renders, per the stacking contract); | |
This pane's current HEIGHT sizing mode (static/flexible) — drives the active control state. | |||
boolean | Whether this pane is the source of the in-flight drag. | ||
boolean | Whether this pane is a valid drop destination for the current drag. | ||
boolean | Whether this pane is the resolved drop target of the in-flight drag. | ||
boolean | Whether this pane currently holds focus. | ||
boolean | Whether the cursor is currently hovering this pane as a drop candidate. | ||
boolean | Whether the current hover over this pane would be an invalid drop. | ||
boolean | Whether this pane is currently maximized (fills the tiling viewport). | ||
boolean | Whether the maximize capability is enabled (controls header button visibility). | ||
boolean | Whether this pane is the SOURCE of an in-flight keyboard move (the keyboard analog of a drag source). Drives the "MOVING" affordance. | ||
boolean | Whether THIS pane is currently a member of the multi-selection set. | ||
boolean | Whether the Alt/Opt+click header multi-selection feature is live ( | ||
boolean | Global pane-content visibility toggle from the tab strip checkbox. | ||
boolean | Whether drag-to-rearrange is enabled (drives handle affordance). | ||
boolean | Whether the per-pane title-bar directional acquire-space controls (→ ← ↑ ↓) are enabled. | ||
boolean | Whether the per-pane title-bar sizing control (FLEX / STATIC H / STATIC W / BOTH) is enabled. | ||
string | The leaf node id this render targets. | ||
TilingMovePlacement | null | When this pane is the pending move-mode DESTINATION, the edge of this pane the moved source will land on ( | ||
(direction: TilingFocusDirection) => void | Grow THIS pane to claim the maximum available space in | ||
(event?: React.SyntheticEvent<HTMLElement>) => void | Establish single focus on this pane (and clear any in-progress multi-selection). Wire to the pane root's | ||
(clickedLeafId: string) => void | Group the current multi-selection into ONE flat tabbed group occupying the CLICKED pane's slot (pass this pane's | ||
(event: React.PointerEvent<HTMLElement>) => void | Pointer-Events drag pickup on the pane's drag handle (the title-bar grip). Wire this to the handle's | ||
(event: React.PointerEvent<HTMLElement>) => void | Clears this pane's hover telemetry when the pointer leaves it. | ||
(event: React.PointerEvent<HTMLElement>) => void | Pre-drag hover telemetry (drop-intent hit-log); inert while a drag is in flight. | ||
(mode: TilingTitleBarSizingMode) => void | Set THIS pane's sizing mode. STATIC modes measure the pane's current bbox (getBoundingClientRect on its | ||
() => void | Toggle maximize/restore for this pane. | ||
() => void | Toggle THIS pane in/out of the multi-selection set. Wire to an Alt/Opt+click on the pane header. Does not change focus. | ||
Canonical pane-body visibility decision resolved by the renderer policy. Keeps custom tile renderers aligned with the default drag/hidden semantics. | |||
number | 1-based pane ordinal in current tab order (for generic pane labels). | ||
number | Current pane viewport width in pixels (for responsive header/control density). | ||
TilingLeafDropPreview | null | The projected landing/result preview for this pane, or | ||
| Which renderer surface these args paint (see TilingRenderSurface). | ||
The resolved tile payload for this leaf. | |||
This pane's current WIDTH sizing mode (static/flexible) — drives the active control state. |
TilingTile
interfaceGeneric tile payload. Only id + title are required so a product consumer (e.g. a dashboard) can supply a minimal { id, title, content } tile and a custom renderTile. The accent / rows fields drive DefaultTilingTile and the drag-pane snapshot chrome; when omitted they fall back (accent → "cyan", rows → []), so a tile without them renders correctly under the default tile surface. content is the slot a custom renderTile reads.
Signature:
interface TilingTile
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Optional per-tile identity accent. Falls back to | |||
React.ReactNode | (Optional) Rich body a custom | ||
string | (Optional) Optional one-line description surfaced by the default tile chrome. | ||
string | Stable tile identity; leaf nodes reference a tile by this | ||
ReadonlyArray<string> | (Optional) Optional text rows for the default tile body. Falls back to | ||
string | Title shown in the pane header / tab. |
TilingTileAccent
typePer-pane identity accent. A closed, typed palette so a consumer can drive a picker from the enumerable TILING_TILE_ACCENTS list (exported from the renderer) with exhaustive type-checking — adding a member here forces every accent theme map to cover it.
Signature:
type TilingTileAccent = "cyan" | "sky" | "violet" | "indigo" | "emerald" | "amber" | "rose" | "pink";
Layout & query
The layout tree node kinds you own in state, the config, and queryTilingLayout for reading structure back out.
DEFAULT_TILING_LAYOUT_CONFIG
variableRecommended baseline layout config — the generic gap / min-pane / handle scale. config stays a required renderer prop (spacing is explicit at the call site), but consumers that don't tune spacing can spread this for a tasteful, well-readable inter-pane gutter out of the box instead of inventing their own magic numbers. gapPx + handleSizePx together form the visible inter-pane gap (the divider element occupies handleSizePx flanked by gapPx / 2 margins; each child subtracts (gapPx + handleSizePx) / 2 of basis — see splitGapOffsetPx in the split renderer for the balancing math).
Signature:
DEFAULT_TILING_LAYOUT_CONFIG: TilingLayoutConfig
queryTilingLayout()
functionBuild a read-only TilingLayoutQuery over layout. The single higher-level layout-inspection helper on the public API: it composes the low-level tree walkers (which stay on the ./engine escape hatch) into the leaf/tile/group/split views and directional-neighbor lookup an app needs to render layout-aware controls.
Signature:
declare function queryTilingLayout(layout: TilingLayoutNode): TilingLayoutQuery;
Parameters
Parameter | Type | Description |
|---|---|---|
layout |
Returns:
Example
Inspect the tree you own in state — count panes, list groups, or find a directional neighbor — without walking it by hand:
import { queryTilingLayout, type TilingLayoutNode } from "@n-uf/hypr-tiling";
function paneCount(layout: TilingLayoutNode): number {
return queryTilingLayout(layout).leafIds.length;
}
const query = queryTilingLayout(layout);
const rightOfFocus = query.neighborLeafId(focusedLeafId, "right");
TilingGroupNode
interfaceA group/member slot (HT-GROUP-TABBED-STACKING): N leaves share ONE layout slot as a stacked group with a tab strip — only activeMemberId renders / is hit-tested, the tabs switch the active member. A group is a SLOT (like a leaf), so a slot in either dwindle or master layout can hold a group. Members are leaves only (no nested split/group — a group of one is degenerate and collapses back to a bare leaf).
Signature:
interface TilingGroupNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | Always one of | ||
string | Stable node identity, unique within the layout tree. | ||
"group" | Node discriminant. Always | ||
ReadonlyArray<TilingLeafNode> | ≥1 member; array order is the tab order. | ||
(Optional) A group can be static like a leaf (per-dimension static/flexible sizing). |
TilingLayoutConfig
interfaceGlobal geometry configuration for the renderer (the config prop).
Signature:
interface TilingLayoutConfig
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Gap (CSS px) painted in the gutters between panes. | ||
number | Resize-handle hit-target thickness (CSS px). | ||
number | Minimum pane extent (CSS px) a resize divider will not shrink a pane below. |
TilingLayoutNode
typeA node in the layout tree: a leaf (single pane), a split (two subtrees), or a group (tabbed leaves in one slot). The recursive union you own in state and feed to TilingRenderer via the layout prop.
Signature:
type TilingLayoutNode = TilingLeafNode | TilingSplitNode | TilingGroupNode;
References: TilingLeafNode, TilingSplitNode, TilingGroupNode
TilingLayoutQuery
interfaceA read-only structural view of a TilingLayoutNode tree, aggregating the queries an app typically needs to drive layout-aware UI (shortcut chips, pane counters, directional focus) without walking the tree by hand or reaching for the low-level ./engine walkers.
Signature:
interface TilingLayoutQuery
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| ReadonlyArray<TilingGroupNode> | Every group in the tree (depth-first, reading order) — the tab-strip source. | |
| boolean | Whether any split node is in | |
| ReadonlyArray<string> | The leaf ids the OUTER layout sees, in reading order — a leaf contributes its id; a group contributes ONLY its active member id (the group presents as one pane to the outer layout). | |
| (fromLeafId: string, direction: TilingFocusDirection) => string | null | The leaf id reached from | |
| ReadonlyArray<TilingSplitNode> | Every split node in the tree (depth-first, reading order). | |
| ReadonlyArray<string> | The tile ids the tree renders, in reading order (group members reported in tab order). Drives tile-order-dependent UI such as a tab strip. |
TilingLeafNode
interfaceA leaf slot: a single pane that renders one tile. The renderer's smallest addressable layout unit — focus, drag, resize, and sizing all target a leaf by its id.
Signature:
interface TilingLeafNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | Stable node identity, unique within the layout tree. | ||
"leaf" | Node discriminant. Always | ||
(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible. | |||
string | The |
TilingSplitNode
interfaceA binary split node: divides its region between two child subtrees along axis by ratio. In layoutMode: "master" the binary structure defines slot membership/order while the master resolver drives geometry.
Signature:
interface TilingSplitNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Split orientation ( | |||
The first (top/left) child subtree. | |||
number | (Optional) Optional per-split gap override (CSS px) between the two children. | ||
string | Stable node identity, unique within the layout tree. | ||
"split" | Node discriminant. Always | ||
(Optional) Arrangement of this subtree's slots. Undefined → | |||
number | (Optional) | ||
(Optional) | |||
number | (Optional) Optional per-split minimum pane extent (CSS px) enforced on resize. | ||
number | Fraction | ||
The second (bottom/right) child subtree. | |||
(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible. |
Theming
Built-in themes, per-pane accents, the theme provider, and useTilingTheme for reading tokens inside a custom pane.
DEFAULT_TILING_THEME_ID
variableDefault library theme id (preserves the prior look at the round-1 checkpoint).
Signature:
DEFAULT_TILING_THEME_ID: TilingThemeId
resolveTilingTheme()
functionResolve a theme id (or the default) to its TilingTheme.
Signature:
declare function resolveTilingTheme(themeId: TilingThemeId | undefined): TilingTheme;
Parameters
Parameter | Type | Description |
|---|---|---|
themeId | TilingThemeId | undefined |
Returns:
TILING_THEMES
variableOrdered, enumerable theme list — the generic capability a theme switcher iterates.
Signature:
TILING_THEMES: readonly TilingTheme[]
TilingTheme
interfaceA complete theme: the static surface token groups plus the accent-composition resolvers. The resolvers are the contract for "how per-pane accents compose with the theme" — each theme decides how much of an accent's hue/glow it spends on the resting surface, title text, focus frame, and active tab.
Signature:
interface TilingTheme
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Split-divider / gap handle tokens. | ||
| Drag-ghost shell tokens. | ||
| TilingThemeId | (string & {}) | The theme's id. A built-in theme uses a | |
| string | Human-readable theme label (e.g. for a theme picker). | |
| Pane header chrome tokens. | ||
| Pane host shell surface + interaction-state tokens. | ||
| (accent: TilingTileAccent | undefined) => string | Accent title-text color. | |
| (accent: TilingTileAccent | undefined) => string | Full focused-pane frame (structural border/ring + accent glow). | |
| (accent: TilingTileAccent | undefined) => string | Resting pane accent composition (border tint + colored shadow). | |
| (accent: TilingTileAccent | undefined) => string | Active tab / switcher / group-member chip. | |
| Renderer-root + viewport surface tokens. | ||
| Top-bar / tab-strip chrome tokens. |
TilingThemeId
typeClosed set of built-in visual theme ids for the renderer. A theme bundles the class-string tokens for every surface (pane shell, header, focus frame, ghost, dividers, root, tab strip) plus the accent-composition resolvers. The token interfaces + registry live in ./theme; this union is the central contract a consumer types its themeId state against.
Signature:
type TilingThemeId = "neon-terminal" | "clean-flat" | "mosaic";
TilingThemeProvider()
functionProvides the active theme to every renderer subcomponent.
Signature:
declare function TilingThemeProvider(input: {
theme: TilingTheme;
children: React.ReactNode;
}): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ theme, children, } | (not declared) | |
input | { theme: TilingTheme; children: React.ReactNode; } |
Returns:
React.ReactElement
useTilingTheme()
functionReads the active theme from context (defaults to the library default).
Signature:
declare function useTilingTheme(): TilingTheme;
Returns:
Commands
The imperative command handle and the typed command union you dispatch from your own controls.
TilingCommand
typeThe public, typed, dispatch-style command set — the public API's imperative entry point (the Hyprland dispatch analog). Every internal renderer action is enumerated here so an embedding app can invoke tiler behavior programmatically (via the TilingCommandHandle) and so a keyboard binding can target any of them (TilingKeyBinding).
Context-dependent commands carry an OPTIONAL leafId; when omitted the renderer resolves it against the CURRENT focused leaf at dispatch time (the "act on the focused pane" ergonomic). Commands that need explicit operands (swap / insert / split ops) require them.
Signature:
type TilingCommand = {
kind: "focus-pane";
leafId: string;
} | {
kind: "focus-direction";
direction: TilingFocusDirection;
} | {
kind: "focus-cycle";
direction: TilingPaneCycleDirection;
} | {
kind: "focus-jump";
paneNumber: number;
} | {
kind: "focus-current-or-last";
} | {
kind: "toggle-maximize";
leafId?: string;
} | {
kind: "maximize";
leafId?: string;
} | {
kind: "restore";
} | {
kind: "enter-move-mode";
leafId?: string;
} | {
kind: "move-aim";
direction: TilingFocusDirection;
} | {
kind: "commit-move-mode";
} | {
kind: "cancel-move-mode";
} | {
kind: "swap-panes";
sourceLeafId: string;
targetLeafId: string;
} | {
kind: "insert-adjacent";
sourceLeafId: string;
targetLeafId: string;
placement: TilingMovePlacement;
} | {
kind: "acquire-space";
leafId?: string;
direction: TilingFocusDirection;
} | {
kind: "set-sizing";
leafId?: string;
mode: TilingTitleBarSizingMode;
} | {
kind: "set-split-ratio";
splitId: string;
ratio: number;
} | {
kind: "toggle-split-axis";
splitId: string;
} | {
kind: "set-layout-mode";
splitId?: string;
mode: TilingLayoutMode;
} | {
kind: "cycle-layout-mode";
splitId?: string;
} | {
kind: "set-master-count";
splitId?: string;
count: number;
} | {
kind: "adjust-master-count";
splitId?: string;
delta: number;
} | {
kind: "set-master-orientation";
splitId?: string;
orientation: TilingMasterOrientation;
} | {
kind: "cycle-master-orientation";
splitId?: string;
} | {
kind: "adjust-master-ratio";
splitId?: string;
delta: number;
} | {
kind: "group-leaves";
leafIds: ReadonlyArray<string>;
hostLeafId?: string;
} | {
kind: "toggle-group";
leafId?: string;
} | {
kind: "ungroup";
groupId?: string;
} | {
kind: "add-to-group";
groupId: string;
sourceLeafId: string;
} | {
kind: "remove-from-group";
groupId: string;
memberId: string;
} | {
kind: "group-tab-cycle";
groupId?: string;
direction: TilingPaneCycleDirection;
} | {
kind: "group-tab-jump";
groupId?: string;
memberNumber: number;
};
References: TilingFocusDirection, TilingPaneCycleDirection, TilingMovePlacement, TilingTitleBarSizingMode, TilingLayoutMode, TilingMasterOrientation
TilingCommandHandle
interfaceThe imperative handle exposed via ref on TilingRenderer. A consumer holds the ref and drives the tiler programmatically — dispatch routes a command through the SAME internal router the keyboard layer uses, so a disabled-capability command is a safe no-op (it never mutates the layout).
Signature:
interface TilingCommandHandle
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(command: TilingCommand) => void | Route a TilingCommand through the renderer's command router. |
Advanced helpers
Power-user helpers and the resolved / capability / keymap / debug type surface — isCommandEnabled, the interaction-capability shapes, and the query utilities you reach for only when building custom command bars, keyboard layers, and observability. Each carries a consumer-usage @example in its hover-docs.
DEFAULT_DRAG_ANIMATION_SPEED_PERCENT
variableDefault drag animation speed percent (100 = the baseline duration).
Signature:
DEFAULT_DRAG_ANIMATION_SPEED_PERCENT: number
DEFAULT_DRAG_HOP_EASING
variableThe default ghost hop / pickup / hop-out timing function (a snappy decel).
Signature:
DEFAULT_DRAG_HOP_EASING: string
DEFAULT_DRAG_REFLOW_EASING
variableThe default survivor-reflow settle timing function. Equals the hop curve so the ghost and the survivors read as one coordinated motion (the renderer's dragReflowEasing prop falls back to the resolved dragHopEasing when undefined, so this default only applies when BOTH are unset).
Signature:
DEFAULT_DRAG_REFLOW_EASING: string
DEFAULT_TILE_ACCENT
variableFirst palette member — the fallback when a tile omits accent.
Signature:
DEFAULT_TILE_ACCENT: TilingTileAccent
DRAG_ANIMATION_SPEED_MAX_PERCENT
variableFastest (ceiling) drag animation speed percent the slider + clamp allow.
Signature:
DRAG_ANIMATION_SPEED_MAX_PERCENT: number
DRAG_ANIMATION_SPEED_MIN_PERCENT
variableSlowest (floor) drag animation speed percent the slider + clamp allow.
Signature:
DRAG_ANIMATION_SPEED_MIN_PERCENT: number
isCommandEnabled()
functionWhether command would do anything given the current capability gates: true when the command's required capability is enabled (or it requires none). A keyboard binding to a disabled-capability command stays browser-graceful (the caller does not preventDefault); an imperative dispatch of one is a no-op.
Signature:
declare function isCommandEnabled(command: TilingCommand, gates: TilingCommandGates): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
command | ||
gates |
Returns:
boolean
Example
Build your own command bar / keyboard shortcut that only fires (and only renders its button) when the target command is actually enabled — the gate the renderer itself uses for its shortcut chips:
import {
resolveInteractionCapabilities,
isCommandEnabled,
type TilingCommand,
type TilingCommandGates,
type TilingCommandHandle,
type TilingInteractionCapabilities,
} from "@n-uf/hypr-tiling";
function gatesFor(interaction?: TilingInteractionCapabilities): TilingCommandGates {
const caps = resolveInteractionCapabilities(interaction);
return {
maximizeEnabled: caps.maximize.enable,
paneSwitchingEnabled: caps.paneSwitching.enable,
focusEnabled: caps.focus,
rearrangeEnabled: caps.rearrange,
sizingEnabled: caps.paneTitleBarControls.sizing,
acquireSpaceEnabled: caps.paneTitleBarControls.acquireSpace,
resizeEnabled: caps.resize !== "none",
layoutEnabled: caps.masterLayout,
groupingEnabled: caps.grouping.enable,
};
}
function MaximizeButton(props: {
handle: React.RefObject<TilingCommandHandle | null>;
interaction?: TilingInteractionCapabilities;
}) {
const command: TilingCommand = { kind: "toggle-maximize" };
if (!isCommandEnabled(command, gatesFor(props.interaction))) {
return null; // maximize is disabled — don't render a dead control
}
return <button onClick={() => props.handle.current?.dispatch(command)}>Maximize</button>;
}
isMultiSelectModifierActive()
functiontrue when the event carries the multi-select modifier — Alt/Opt (altKey), the single chord both the header toggle and the Alt+G group key key off.
Signature:
declare function isMultiSelectModifierActive(event: MultiSelectModifierState): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
event |
Returns:
boolean
Example
In a custom renderTile, add the clicked pane to the multi-selection only when the platform multi-select modifier is held (a plain click falls through to focus):
import { isMultiSelectModifierActive } from "@n-uf/hypr-tiling";
<header
onClick={(event) => {
if (args.isMultiSelectGroupingEnabled && isMultiSelectModifierActive(event)) {
event.preventDefault();
args.onToggleMultiSelect();
}
}}
/>
MultiSelectModifierState
interfaceThe modifier-key subset of a pointer/mouse event that discriminates a multi-select click from a plain click. The multi-select / grouping modifier is unified on Alt/Opt across every surface (header click + the Alt+G key), chosen for minimal cross-browser interference: altKey = Opt on macOS, Alt on Windows/Linux.
Signature:
interface MultiSelectModifierState
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | Whether Alt/Opt was held (the multi-select discriminator). |
ResolvedTilingDragRecoveryCapability
interfaceFully-resolved drag-recovery configuration (no optional fields).
Signature:
interface ResolvedTilingDragRecoveryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Resolved self-healing recovery enable flag. | ||
number | Resolved rAF-fallback slack (ms). | ||
number | Resolved idle-watchdog deadline (ms). | ||
number | Resolved transition-completion slack (ms). |
ResolvedTilingDropHitZoneGeometryCapability
interfaceFully-resolved drop hit-zone geometry (no optional fields). centerRatio is retained as the symmetric/representative value (equals centerRatioX when no per-axis override diverges them) for telemetry + the single-knob showcase display; centerRatioX / centerRatioY are the per-axis values the resolver actually consumes.
Signature:
interface ResolvedTilingDropHitZoneGeometryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Resolved floor (CSS px) for the center rectangle extent. | ||
number | Resolved symmetric/representative center swap-zone fraction. | ||
number | Resolved per-axis HORIZONTAL swap-zone fraction the resolver consumes. | ||
number | Resolved per-axis VERTICAL swap-zone fraction the resolver consumes. | ||
number | Resolved boundary stickiness (pane-local CSS px). |
ResolvedTilingGroupingCapability
interfaceResolved group / tabbed-stacking capability (no optional fields).
Signature:
interface ResolvedTilingGroupingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether group / tabbed-stacking is enabled. | ||
boolean | Whether the per-group tab strip renders. |
ResolvedTilingInteractionCapabilities
interfaceFully-resolved interaction capabilities (no optional fields). The resolved shape is a structural superset of TilingInteractionCapabilities, so a resolved object can be re-fed to resolveInteractionCapabilities (idempotent) and passed straight back into the interaction prop.
Signature:
interface ResolvedTilingInteractionCapabilities
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether coherent non-intersecting transit is enabled. | ||
boolean | Whether the custom-rendered drag cursor is used during live drag. | ||
Resolved drag feedback mode ( | |||
Resolved drag/transition self-healing recovery configuration. | |||
Resolved adjustable drop hit-zone geometry. | |||
boolean | Whether pane focus selection is enabled. | ||
number | Resolved ghost pickup scale as a percent of the source pane bbox. | ||
Resolved group / tabbed-stacking capability. | |||
Resolved consumer key bindings (the raw registry passed through; an empty binding list + | |||
Resolved keymap (every chord explicit). | |||
boolean | Whether the master/stack layout engine is enabled. | ||
Resolved maximize-to-viewport capability. | |||
Resolved tab-like pane-switching capability. | |||
Resolved per-pane title-bar controls capability. | |||
boolean | Whether drag-to-rearrange is enabled. | ||
Resolved divider resize capability. | |||
boolean | Whether split-divider resize handles are visibly rendered. | ||
Resolved live-drag slot-commitment configuration. | |||
boolean | Whether the single ghost hops into and fills the resolved slot. | ||
Resolved touch-drag hardening configuration. |
ResolvedTilingKeyBindings
interfaceFully-resolved key-binding registry (no optional fields).
Signature:
interface ResolvedTilingKeyBindings
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
ReadonlyArray<TilingKeyBinding> | Resolved consumer chord→command bindings (empty when none supplied). | ||
boolean | Whether the default keymap bindings are suppressed. |
ResolvedTilingKeyChord
interfaceFully-resolved key chord (every modifier explicit). Matches KeyboardEvent.code.
Signature:
interface ResolvedTilingKeyChord
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the Alt key must be held. | ||
string | Physical | ||
boolean | Whether the Ctrl key must be held. | ||
boolean | Whether the Meta (Cmd / Win) key must be held. | ||
boolean | Whether the Shift key must be held. |
ResolvedTilingKeyChordModifiers
interfaceFully-resolved jump-to-pane modifier set.
Signature:
interface ResolvedTilingKeyChordModifiers
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the Alt key must be held. | ||
boolean | Whether the Ctrl key must be held. | ||
boolean | Whether the Meta (Cmd / Win) key must be held. | ||
boolean | Whether the Shift key must be held. |
ResolvedTilingKeymap
interfaceFully-resolved keymap (no optional fields).
Signature:
interface ResolvedTilingKeymap
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Resolved chord to toggle the focused subtree dwindle ⇄ master. | |||
Resolved chord to cycle the master-area orientation. | |||
Resolved chord to remove one tile from the master area. | |||
Resolved chord to shrink the master-area fraction. | |||
Resolved chord to enter keyboard move mode. | |||
Resolved chord for the MRU focus current-or-last toggle. | |||
Resolved chord to move focus to the neighbor BELOW. | |||
Resolved chord to move focus to the LEFT neighbor. | |||
Resolved chord to move focus to the RIGHT neighbor. | |||
Resolved chord to move focus to the neighbor ABOVE. | |||
Resolved chord to activate the next member tab of the focused group. | |||
Resolved chord to activate the previous member tab of the focused group. | |||
Resolved chord to add one tile to the master area. | |||
Resolved chord to grow the master-area fraction. | |||
Resolved modifier set for the | |||
Resolved chord to cycle to the next pane. | |||
Resolved chord to cycle to the previous pane. | |||
Resolved chord to restore from maximize. | |||
Resolved chord to group/ungroup the focused pane. | |||
Resolved chord to toggle maximize on the focused pane. |
ResolvedTilingMaximizeCapability
interfaceResolved maximize capability (no optional fields).
Signature:
interface ResolvedTilingMaximizeCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the maximize/restore control + shortcuts are live. |
ResolvedTilingPaneSwitchingCapability
interfaceResolved pane-switching capability (no optional fields).
Signature:
interface ResolvedTilingPaneSwitchingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether pane switching (tab strip + cycle/jump shortcuts) is live. | ||
boolean | Whether Alt/Opt+click header multi-selection grouping is live. | ||
boolean | Whether the tab strip's pane-content visibility checkbox renders. | ||
boolean | Whether the Cmd+Tab-style switcher overlay renders while cycling. | ||
boolean | Whether the tab strip renders. | ||
boolean | Whether double-clicking a tab toggles that pane's maximize state. |
ResolvedTilingPaneTitleBarControlsCapability
interfaceResolved per-pane title-bar control capability (no optional fields).
Signature:
interface ResolvedTilingPaneTitleBarControlsCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the per-pane directional acquire-space controls render. | ||
boolean | Whether the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control renders. |
ResolvedTilingSlotCommitmentCapability
interfaceFully-resolved slot-commitment configuration (no optional fields).
Signature:
interface ResolvedTilingSlotCommitmentCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Resolved re-resolution policy after the ghost seats in a slot. | |||
number | Resolved |
ResolvedTilingTouchDragCapability
interfaceFully-resolved touch-drag configuration (no optional fields).
Signature:
interface ResolvedTilingTouchDragCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Resolved touch-drag enable flag. | ||
number | Resolved long-press delay (ms) before a held touch becomes a drag. |
resolveInteractionCapabilities()
functionSingle defaulting helper for TilingInteractionCapabilities. undefined / null → all enabled; a partial override merges field-by-field over the all-enabled defaults. Uses nullish coalescing so an explicit false (e.g. rearrange: false, maximize.enable: false) is preserved and never overridden by the default. Idempotent: re-resolving a resolved object yields the same result.
Signature:
declare function resolveInteractionCapabilities(capabilities?: TilingInteractionCapabilities | null): ResolvedTilingInteractionCapabilities;
Parameters
Parameter | Type | Description |
|---|---|---|
capabilities | (Optional) |
Returns:
resolveJumpedPaneId()
functionResolve the jump-to-N leaf id (1-based), or null when out of range (no-op).
Signature:
declare function resolveJumpedPaneId(leafIds: ReadonlyArray<string>, paneNumber: number): string | null;
Parameters
Parameter | Type | Description |
|---|---|---|
leafIds | ReadonlyArray<string> | |
paneNumber | number |
Returns:
string | null
Example
Wire your own "jump to pane N" buttons against the leaf order from queryTilingLayout(), dispatching the built-in focus-jump command:
import { queryTilingLayout, resolveJumpedPaneId } from "@n-uf/hypr-tiling";
const { leafIds } = queryTilingLayout(layout);
leafIds.forEach((_, i) => {
const paneNumber = i + 1;
const targetLeafId = resolveJumpedPaneId(leafIds, paneNumber); // null → out of range
// render a button that dispatches { kind: "focus-jump", paneNumber } when targetLeafId != null
});
TILING_ACCENT_HUES
variableThe hue atoms for every accent. Keyed by the closed TilingTileAccent union so the compiler enforces full coverage. Theme-independent: themes choose which atoms to apply.
Signature:
TILING_ACCENT_HUES: Record<TilingTileAccent, TilingAccentHue>
TILING_DASHBOARD_PRESET
variableInteraction preset for static (config-driven) product dashboards: only height dividers resize (resize: "vertical"), and every interactive surface that assumes a rearrangeable/maximizable lab layout is off (no drag-rearrange, no pane focus selection, no maximize, no pane switching / tab strip). Dashboards pass this instead of hand-repeating the disable set.
Per-pane title-bar controls (sizing + acquire-space) are OFF here too: dashboards ship a curated layout with PRE-CONFIGURED static panes and are resize-only by intent, so letting an end-user re-pin a pane's bbox or have one pane absorb its siblings' space would fight the authored composition. End-user per-pane sizing belongs to interactive workspaces (the showcase default), not config-driven dashboards.
Signature:
TILING_DASHBOARD_PRESET: TilingInteractionCapabilities
TILING_INTERACTION_CAPABILITY_DEFAULTS
variableAll-enabled defaults. An undefined capability config (or any undefined field) resolves to these via resolveInteractionCapabilities.
The lone opt-IN exception is paneSwitching.showContentToggle: it defaults to false. That checkbox (a group tab strip's "show pane body" toggle) is a development / demo affordance — a consumer app renders its own pane content and never wants an end-user control that blanks it. Suppressed by default, the initial pane-content-visible flag pins ON (see resolveInitialPaneContentVisible), so panes paint their content at rest with no wiring. A tooling surface that genuinely wants the toggle (e.g. the interactive showcase) opts back in with paneSwitching: { showContentToggle: true }.
Signature:
TILING_INTERACTION_CAPABILITY_DEFAULTS: ResolvedTilingInteractionCapabilities
TILING_THEME_REGISTRY
variableBuilt-in theme registry, keyed by the closed TilingThemeId union. Adding a member to TilingThemeId forces a new entry here (the Record fails to compile until filled in).
Signature:
TILING_THEME_REGISTRY: Record<TilingThemeId, TilingTheme>
TILING_TILE_ACCENT_SWATCHES
variablePicker-ready metadata (accent + label + swatch class) for every accent.
Signature:
TILING_TILE_ACCENT_SWATCHES: readonly TilingTileAccentSwatch[]
TILING_TILE_ACCENTS
variableOrdered, enumerable accent palette — the generic capability a consumer iterates to build an accent picker.
Signature:
TILING_TILE_ACCENTS: readonly TilingTileAccent[]
TilingAccentHue
interfacePer-accent HUE tokens — the raw color atoms a theme composes. Decoupled from the previous bundled accent theme so a calm theme can borrow an accent's hue (border/text/ring) WITHOUT inheriting its heavy neon glow, and a refined theme can pick the softened glow. Every field is a literal Tailwind class so the JIT emits it.
Signature:
interface TilingAccentHue
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Focused-pane border color. | |
| string | Full-intensity neon focus glow (box-shadow). | |
| string | Dialed-back focus glow for refined / calm themes (box-shadow). | |
| string | Focused-pane focus-ring color. | |
| string | Human-facing palette label (also the picker swatch label). | |
| string | Resting pane border tint (low alpha). | |
| string | Resting pane colored drop-shadow tint. | |
| string | Solid background for a palette swatch dot. | |
| string | Active tab/switcher chip translucent fill. | |
| string | Subtle active-tab fill for low-contrast themes. | |
| string | Active tab/switcher chip border. | |
| string | Accent title / metadata text. | |
| string | Strong accent text used on active chips. |
TilingCommandGates
interfaceThe capability enable-flags a command may require. Superset of TilingKeymapActionGuards (which only covers the keyboard-reachable actions) with the sizing / acquire-space / resize gates the programmatic command set also touches. The renderer builds this from its resolved capabilities.
Signature:
interface TilingCommandGates
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Directional acquire-space command. | ||
boolean | Focus selection commands. | ||
boolean | Group / tabbed-stacking commands (HT-GROUP-TABBED-STACKING). | ||
boolean | Master/stack layout-mode commands (HT-LAYOUT-MASTER-STACK). | ||
boolean | Maximize / restore commands. | ||
boolean | Pane-switching (focus-cycle / focus-jump) commands. | ||
boolean | Drag/keyboard rearrange (move / swap / insert) commands. | ||
boolean | Divider resize ( | ||
boolean | Per-pane title-bar sizing ( |
TilingDragHandle()
functionThe drag-pickup surface of a custom pane (typically the header). Wires the renderer's onHandlePointerDown and sets touch-action: none so a touch press starts a drag instead of scrolling, and folds in the Alt/Opt+click multi-select toggle (which must not establish focus). Renders a <div>; bring your own className / style / children.
Signature:
declare function TilingDragHandle(input: TilingDragHandleProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, style, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingDragHandleProps
interfaceProps for TilingDragHandle().
Signature:
interface TilingDragHandleProps extends Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick">
Extends: Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick">
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "onHandlePointerDown" | "isMultiSelectGroupingEnabled" | "onToggleMultiSelect"> | The |
TilingDragMode
typeDrag-to-rearrange feedback mode — how a pending move is shown WHILE dragging, before it is committed on drop.
"preview"— non-committing preview (default). The pending result is shown via the translucent PROJECTED landing overlays (S' / T' / successor) painted over the unchanged layout; the layout tree is NOT mutated until drop. -"live"— Hyprland-faithful detach drag. On pickup the dragged source leaf is detached and the remaining tree reflows ONCE to close the gap (removeLeafTile); that frozen provisional tree is held for the whole drag (it does NOT re-reflow as the cursor moves). The detached source follows the cursor as a ghost, and the drop is resolved + committed exactly ONCE on release against the original layout via the SAME resolver + reducers preview uses (swapLeafTiles/insertLeafAdjacent), so the live commit equals the preview commit for the same intent. The prop tree is never mutated pre-commit, so cancel / Escape / dragleave-abort / off-viewport revert is lossless.
NOTE on vocabulary: "projected" names the RENDERING TECHNIQUE for the preview overlays (projected geometry, S' / T' / successor). "preview" vs "live" is the interaction MODE. The mode is never called "projected".
Signature:
type TilingDragMode = "preview" | "live";
TilingDragRecoveryCapability
interfaceDrag / transition self-healing recovery configuration. Hardens the live-drag ANIMATION + TIMING layer (not the FSM, which is already un-wedgeable) against frame starvation — background-tab requestAnimationFrame suspension, CPU throttling, long tasks, dropped/late pointer events, interrupted transitions. The recovery primitives live in drag-recovery.ts; this is their typed knob surface. All deadlines are documented multiples of the animation duration or of a frame, never symptom-tuned constants. Only meaningful in dragMode: "live".
enable— master gate for the recovery layer (idle watchdog + rAF-with-timeout arming + idempotent style teardown). Defaulttrue. -maxDraggingIdleMs— the drag idle-watchdog deadline:armed/draggingwith noPOINTER_MOVE/TARGET_RESOLVEDprogress for longer than this (measured MONOTONICALLY, so throttle-robust) force-reconciles toidlevia the existingPOINTER_CANCELedge with pointer capture released. Default30 × BASELINE_DRAG_HOP_DURATION_MS ≈ 5100ms. -frameDeadlineMs— the rAF-fallback slack: each FLIP "play-to-identity" write is armed as arequestAnimationFrameracing asetTimeoutof this long, first-wins + idempotent, so a starved frame never leaves an element frozen at its invertedFirst. Default~2 frames ≈ 32ms. -transitionSlackMs— the transition-completion slack: style cleanup fires ontransitionendORduration + transitionSlackMs, whichever first. Default60ms(names the existing+60mask-close slack).
Signature:
interface TilingDragRecoveryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable the self-healing recovery layer. Default | ||
number | (Optional) rAF-fallback slack (ms). Default | ||
number | (Optional) Idle-watchdog deadline (ms). Default | ||
number | (Optional) Transition-completion slack (ms). Default |
TilingDropAction
typeThe outcome a drop resolves to: exchange tiles ("swap"), insert at an edge ("edge-insert"), insert into an existing split container ("split-container-insert"), merge into a tabbed group ("group-merge"), or no valid action ("none").
Signature:
type TilingDropAction = "swap" | "edge-insert" | "split-container-insert" | "group-merge" | "none";
TilingDropHitZoneGeometryCapability
interfaceAdjustable drag-drop HIT-ZONE GEOMETRY. These knobs shape the five-region partition every pane uses to map a cursor position to a drop intent: one central SWAP rectangle plus four directional INSERT trapezoids (top / right / bottom / left) formed by the diagonals from each pane corner to the nearest center-rectangle corner (see drop-intent-resolver.ts). The partition tiles the full pane with no gaps; the same geometry drives both resolution and the visual overlay, so the drawn zone is exactly the resolved zone.
The model is SYMMETRIC: a single centerRatio sizes the center rectangle on BOTH axes, which means the directional edge band depth is fully derived from it as (1 - centerRatio) / 2 per axis (there is no independent edge-band knob, and no separate horizontal/vertical asymmetry — the diagonal-trapezoid construction uses one ratio). CORNERS are not a distinct zone: a corner resolves to whichever edge trapezoid contains it, with a deterministic tie-break order (top → right → bottom → left) on an exact diagonal.
Every field is optional; an undefined field resolves to the documented default (which equals today's TILING_DROP_INTENT_CONFIG), so omitting this object leaves drop-zone behavior exactly as it was.
Signature:
interface TilingDropHitZoneGeometryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | (Optional) Floor (CSS px) for the center rectangle's extent on each axis so tiny panes keep a usable swap target even when | ||
number | (Optional) Fraction of each pane axis spanned by the central SWAP rectangle. The directional INSERT edge band depth is the complement, | ||
number | (Optional) Per-axis HORIZONTAL (width) swap-zone fraction override. When set, it sizes the center rectangle's X extent independently of | ||
number | (Optional) Per-axis VERTICAL (height) swap-zone fraction override. When set, it sizes the center rectangle's Y extent independently of | ||
number | (Optional) Boundary stickiness (pane-local CSS px): once the cursor is in a zone it must cross the boundary by this much before the classification switches, suppressing sub-pixel flicker at the trapezoid edges. |
TilingFocusDirection
typeA directional focus / move target relative to the current pane.
Signature:
type TilingFocusDirection = "left" | "right" | "up" | "down";
TilingGroupingCapability
interfaceGroup / tabbed-stacking capability (HT-GROUP-TABBED-STACKING), the object form of the grouping capability. A bare boolean at the grouping slot is shorthand for { enable }.
Signature:
interface TilingGroupingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable grouping. When | ||
boolean | (Optional) Render the per-group tab strip (the member tabs a tabbed-stacking group paints above its active member). Default |
TilingGroupMemberView
interfaceOne member of a tabbed group, as seen by a custom renderTile through TilingRenderTileGroupContext.members. Mirrors what the built-in group tab strip paints per tab: the member's identity, its resolved tile payload, its 1-based position (the group-tab-jump operand — also the keyboard Alt+<n> label), and whether it is the active (rendered) member.
Signature:
interface TilingGroupMemberView
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | Whether this member is the group's active (rendered) member. | |
| string | The member's leaf node id. | |
| number | 1-based position in the group's member order — the operand TilingRenderTileGroupContext.activateMember takes (and the number the built-in strip / keyboard | |
| TilingTile | null | The resolved tile payload, or | |
| string | The tile id the member's leaf points at. |
TilingInteractionCapabilities
interfacePublic, reactive interaction-capability flags for the tiling renderer. All fields are optional; an undefined field (or an undefined config object) resolves to the all-enabled default via resolveInteractionCapabilities.
Signature:
interface TilingInteractionCapabilities
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Coherent non-intersecting transit. When | ||
boolean | (Optional) Custom-rendered drag cursor (interaction tier "c"). When | ||
(Optional) Drag-to-rearrange feedback mode: | |||
(Optional) Drag / transition self-healing recovery (idle watchdog + rAF-with-timeout animation arming + idempotent transient-style teardown). Hardens the live-drag animation/timing layer against frame starvation. Only meaningful in | |||
(Optional) Adjustable drag-drop hit-zone geometry (center swap fraction, center floor, boundary hysteresis). Undefined → today's | |||
boolean | (Optional) Pane focus selection. When | ||
number | (Optional) Ghost pickup scale as a percent of the source pane's full bbox. On drag start the live-mode ghost animates from the source pane's full bbox to this fraction of it; values <100% read as lifted/shrunk. The free-following ghost rests at this scale and morphs toward the resolved slot's bbox on hop-in. Range | ||
boolean | TilingGroupingCapability | (Optional) Group / tabbed-stacking (HT-GROUP-TABBED-STACKING). A bare boolean is shorthand for | ||
(Optional) Public chord→command binding registry. Consumer bindings augment (and on a chord collision override) the default keymap bindings; set | |||
(Optional) Top-level keymap. Capability-level | |||
boolean | (Optional) Master/stack layout engine (HT-LAYOUT-MASTER-STACK). When | ||
(Optional) Maximize-to-viewport capability. Default all-enabled. | |||
(Optional) Tab-like pane-switching capability. Default all-enabled. | |||
(Optional) Per-pane title-bar controls (in-header sizing + acquire-space). Default all-enabled. Reactive: toggling a flag at runtime shows/hides the controls without remount. | |||
boolean | (Optional) Drag-to-rearrange (move / swap / edge-insert) capability. When | ||
(Optional) Divider resize capability. Default | |||
boolean | (Optional) Whether split-divider resize handles are visibly rendered. | ||
(Optional) Live-drag slot re-resolution / commitment policy (the single ghost hops INTO and FILLS the resolved slot). Only meaningful in | |||
boolean | (Optional) Live-drag slot hop-in. Selects between the two drag-presentation behaviors for the dragged pane:
Orthogonal to the CONTENT toggle: in BOTH modes the pane body honors content uniformly (see | ||
(Optional) Touch-drag hardening (touch enable + long-press disambiguation). Only meaningful when |
TilingKeyBinding
interfaceA single keyboard binding: an arbitrary chord mapped to an arbitrary command. The public chord→command registration surface (the Hyprland bind analog), distinct from the fixed TilingKeymap chord overrides (which only re-chord the built-in action set). The chord matches on KeyboardEvent.code exactly like TilingKeyChord.
Signature:
interface TilingKeyBinding
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The chord that triggers this binding (matched on | |||
The command dispatched when the chord matches. |
TilingKeyBindings
interfaceConsumer keyboard-binding registry. bindings augment (and, on a chord collision, override) the default keymap bindings; setting replaceDefaults suppresses the built-in keymap path entirely so ONLY these bindings are live.
Signature:
interface TilingKeyBindings
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
ReadonlyArray<TilingKeyBinding> | (Optional) Consumer chord→command bindings; augment (or override on collision) the defaults. | ||
boolean | (Optional) When |
TilingKeyChord
interfaceA single keyboard chord: a PHYSICAL KeyboardEvent.code value plus the modifier state required to match it. Absent modifiers resolve to false (the modifier must NOT be held) — a chord fully specifies its modifier requirements.
The chord matches on event.code (the physical key, e.g. "Enter", "Escape", "BracketLeft", "BracketRight"), NOT event.key (the produced character). This is mandatory on macOS, where holding Option(Alt) rewrites event.key into dead-key glyphs (Option+] → "‘", Option+digits → special glyphs), so a event.key-based comparison never matches. event.code is stable across keyboard layouts and modifier state.
Signature:
interface TilingKeyChord
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Require the Alt key. Default | ||
string | Matches | ||
boolean | (Optional) Require the Ctrl key. Default | ||
boolean | (Optional) Require the Meta (Cmd / Win) key. Default | ||
boolean | (Optional) Require the Shift key. Default |
TilingKeyChordModifiers
interfaceModifier requirements for the jump-to-pane digit family (Alt+1..Alt+9). The 1..9 digit is implied (matched against the physical Digit1..Digit9 codes); only the modifier state is configurable.
Signature:
interface TilingKeyChordModifiers
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Require the Alt key. Default | ||
boolean | (Optional) Require the Ctrl key. Default | ||
boolean | (Optional) Require the Meta (Cmd / Win) key. Default | ||
boolean | (Optional) Require the Shift key. Default |
TilingKeymap
interfacePublic, reactive keymap. Every field is optional; an undefined field resolves to its documented default via resolveKeymap. Action-level merge: supplying one binding leaves the others at their defaults.
Signature:
interface TilingKeymap
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Toggle the focused subtree's layout mode dwindle ⇄ master (the Hyprland layout-toggle analog). Default | |||
(Optional) Cycle the master-area orientation left → top → right → bottom → left. Default | |||
(Optional) Remove one tile from the master area ( | |||
(Optional) Shrink the master-area fraction ( | |||
(Optional) Enter keyboard MOVE MODE on the focused pane (the keyboard analog of a drag pickup). Default | |||
(Optional) Toggle focus between the current pane and the most-recently-focused other pane (the MRU "focus current-or-last" toggle; Hyprland | |||
(Optional) Move focus to the geometric neighbor BELOW. Default bare | |||
(Optional) Move focus to the geometric neighbor on the LEFT. Default bare | |||
(Optional) Move focus to the geometric neighbor on the RIGHT. Default bare | |||
(Optional) Move focus to the geometric neighbor ABOVE. Default bare | |||
(Optional) Activate the next member tab of the focused group. Default | |||
(Optional) Activate the previous member tab of the focused group. Default | |||
(Optional) Add one tile to the master area ( | |||
(Optional) Grow the master-area fraction ( | |||
(Optional) Modifier set for the | |||
(Optional) Cycle to the next pane. Default | |||
(Optional) Cycle to the previous pane. Default | |||
(Optional) Restore from maximize. Default | |||
(Optional) Toggle grouping for the focused pane: group it with its reading-order neighbor, or ungroup it if already grouped (the Hyprland | |||
(Optional) Toggle maximize on the focused pane. Default |
TilingLayoutMode
typeArrangement of a split subtree's slots:
"dwindle"— the default recursive binary-split layout: each split distributes its two children along itsaxisbyratio(the original and only Phase-1/2 algorithm). -"master"— a master-area + stack layout (the Hyprlandmasteranalog). The split's descendant slots (leaves / groups, flattened in reading order) are laid out asmasterCountmaster tiles in a master area plus the remaining tiles in a stack; the split'sratiois reused as the master-area fraction andmasterOrientationplaces the master area. The binary structure beneath a master split is flattened (ignored for geometry); it still defines slot membership + order + identity, and the reducers still operate on it.
Signature:
type TilingLayoutMode = "dwindle" | "master";
TilingLeafDropPreview
interfaceA projected landing/result preview shown on a pane during a drag.
Signature:
interface TilingLeafDropPreview
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Whether the preview depicts a swap or an edge-insert. | |||
string | The other leaf involved (swap counterpart or insert neighbor). | ||
Whether this preview is the drag-source landing or the drop-target result. | |||
The drop zone the preview corresponds to. |
TilingLeafDropZone
typeThe five drop regions of a pane: a central swap zone plus four insert edges.
Signature:
type TilingLeafDropZone = "center" | "left" | "right" | "top" | "bottom";
TilingLeafPreviewMode
typeWhether a preview depicts a swap or an edge-insert result.
Signature:
type TilingLeafPreviewMode = "swap" | "edge-insert";
TilingLeafPreviewRole
typeWhich side of a preview a shadow represents (drag source vs drop target).
Signature:
type TilingLeafPreviewRole = "drag-source-landing-shadow" | "drop-target-result-shadow";
TilingMasterOrientation
typeWhere the master area sits in layoutMode: "master". left/right divide the container along WIDTH (master area a column, members stacked vertically); top/bottom divide along HEIGHT (master area a row, members stacked horizontally). The complement holds the stack.
Signature:
type TilingMasterOrientation = "left" | "right" | "top" | "bottom";
TilingMaximizeCapability
interfaceMaximize (expand-to-viewport) capability. The maximize render-mode is non-destructive — it hides sibling panes and renders the focused pane filling the tiling viewport without mutating the layout tree.
Signature:
interface TilingMaximizeCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable the per-pane maximize/restore control + shortcuts. Default | ||
Pick<TilingKeymap, "toggleMaximize" | "restore"> | (Optional) Per-capability keybinding overrides for |
TilingMovePlacement
typeWhich edge of a target pane an inserted/moved pane lands on.
Signature:
type TilingMovePlacement = "left" | "right" | "top" | "bottom";
TilingPaneAction()
functionA header action button (maximize, group, …) that stopPropagations on both pointer-down and click, so activating it never starts a drag or steals pane focus. Defaults type to "button" and calls your onClick after stopping propagation. Bring your own className / style / children.
Signature:
declare function TilingPaneAction(input: TilingPaneActionProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ onClick, type, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneActionProps
typeProps for TilingPaneAction().
Signature:
type TilingPaneActionProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
TilingPaneBody()
functionThe body wrapper of a custom pane. Always renders its wrapper <div> (so the pane keeps its layout), but renders children ONLY when paneBodyRenderMode === "render-content". This keeps a custom pane aligned with the renderer's drag-ghost / hidden-body semantics — the ghost reuses the same render path, so an empty body never rides along. Bring your own className / style.
Signature:
declare function TilingPaneBody(input: TilingPaneBodyProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, children, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneBodyProps
interfaceProps for TilingPaneBody().
Signature:
interface TilingPaneBodyProps extends React.HTMLAttributes<HTMLDivElement>
Extends: React.HTMLAttributes<HTMLDivElement>
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "paneBodyRenderMode"> | The |
TilingPaneBodyRenderMode
typeResolved pane-body render decision: - "render-content" — paint the tile body, - "render-empty" — hidden body (content toggle off), - "render-reservation" — empty ghost-seat reservation slot during a drag.
Signature:
type TilingPaneBodyRenderMode = "render-content" | "render-empty" | "render-reservation";
TilingPaneCycleDirection
typePane cycle direction for the reading-order ring (next / previous, wraparound).
Signature:
type TilingPaneCycleDirection = "next" | "previous";
TilingPaneRoot()
functionThe root element of a custom pane. Renders an <article data-leaf-id> (the attribute the renderer resolves the drag source from) and wires the pane's onFocus (on both focus and click), onPointerMove, and onPointerLeave handlers, so focus, resize, and pre-drag hover telemetry keep working. Bring your own className / style / children; defaults tabIndex to -1.
Signature:
declare function TilingPaneRoot(input: TilingPaneRootProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneRootProps
interfaceProps for TilingPaneRoot().
Signature:
interface TilingPaneRootProps extends Omit<React.HTMLAttributes<HTMLElement>, "onFocus" | "onClick" | "onPointerMove" | "onPointerLeave">
Extends: Omit<React.HTMLAttributes<HTMLElement>, "onFocus" | "onClick" | "onPointerMove" | "onPointerLeave">
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "leafId" | "onFocus" | "onPointerMove" | "onPointerLeave"> | The |
TilingPaneSizing
interfacePer-dimension sizing declaration for a node. Each dimension is independent: a pane can be static in height, in width, or in both. An undefined dimension defaults to "flexible".
The object shape is chosen over an axis list (e.g. staticAxes: ("width" | "height")[]) because it is explicit per-dimension, self-documenting at the call site (sizing: { height: "static" }), and lets a dimension be stated "flexible" explicitly rather than only inferred from absence.
Signature:
interface TilingPaneSizing
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Sizing mode for the HEIGHT dimension. Undefined → | |||
number | (Optional) Pinned HEIGHT in CSS px, captured from the pane's measured bounding box at the moment the user freezes the pane via the title-bar STATIC H / BOTH control. Only meaningful when | ||
(Optional) Sizing mode for the WIDTH dimension. Undefined → | |||
number | (Optional) Pinned WIDTH in CSS px, captured from the pane's measured bounding box at the moment the user freezes the pane via the title-bar STATIC W / BOTH control. Only meaningful when |
TilingPaneSizingMode
typePer-dimension sizing mode for a node placed inside a split.
"flexible"— the node shares space by ratio along the split axis and stretches/reflows along the cross axis (the default for every dimension). -"static"— the node is sized to its INTRINSIC/CONTENT extent along that dimension; it is excluded from ratio distribution and resize dividers when the static dimension runs ALONG the parent split axis, and simply content-sizes (no stretch) when the static dimension is the CROSS axis.
Signature:
type TilingPaneSizingMode = "static" | "flexible";
TilingPaneSwitchingCapability
interfaceTab-like pane-switching capability. Renders a tab strip across the top of the tiling region and binds the cycle / jump shortcuts. When maximized, switching panes also switches which pane is maximized.
Signature:
interface TilingPaneSwitchingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable pane switching (tab strip + cycle/jump shortcuts). Default | ||
Pick<TilingKeymap, "previousPane" | "nextPane" | "jumpToPane"> | (Optional) Per-capability keybinding overrides for | ||
boolean | (Optional) Alt/Opt+click a pane HEADER to toggle that pane into a multi-selection set, then group the selected panes into one flat tabbed group via the existing | ||
boolean | (Optional) Render the tab strip's pane-content visibility checkbox (the "content" toggle that flips the default-tile body between content and empty). Default | ||
boolean | (Optional) Render the macOS Cmd+Tab-style visual switcher overlay while cycling panes with a held modifier ( | ||
boolean | (Optional) Render the TOP-LEVEL tab strip (the single strip across the top of the tiling region listing every pane). Default | ||
boolean | (Optional) Toggle a pane's maximize/restore state when its TAB in the tab strip is double-clicked. Default |
TilingPaneTitleBarControlsCapability
interfacePer-pane TITLE-BAR control capability. These controls render directly in each pane's own header (next to maximize), so they target THAT pane (no central target-pane selector). Two independent control groups:
sizing— the FLEX / STATIC H / STATIC W / BOTH segmented control. STATIC actions measure the pane's current rendered bbox and pin the chosen dimension(s) to that pixel value; FLEX clears the pin. -acquireSpace— the four directional (→ ← ↑ ↓) "grow to claim space" buttons, driven by thegrowLeafTowardreducer.
Signature:
interface TilingPaneTitleBarControlsCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Render the per-pane directional (→ ← ↑ ↓) acquire-space controls. Default | ||
boolean | (Optional) Render the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control. Default |
TilingRenderSurface
typeThe renderer surface a renderTile invocation paints:
"pane"— the seated in-tree pane (the normal, interactive case). -"drag-ghost"— the floating, portaled pickup ghost that travels with the cursor during a drag.aria-hidden+pointer-events-none: handlers are inert no-ops, capability display flags stay real. -"drag-cancel"— the cancel fly-back overlay (the ghost gliding home after a cancelled drag). Same inert-handlers / real-flags semantics as"drag-ghost".
Signature:
type TilingRenderSurface = "pane" | "drag-ghost" | "drag-cancel";
TilingRenderTileGroupContext
interfaceGroup context on TilingRenderTileProps.group: present when the pane being rendered is a tabbed group's ACTIVE member (the only member that renders, per the stacking contract), null for loose leaves and for the drag-ghost / drag-cancel surfaces. A custom pane uses it to paint its own group chrome (tabs, member list, eject/ungroup controls) instead of the built-in strip — typically with the strip suppressed via the grouping capability's showGroupTabStrip: false.
The callbacks route through the SAME internal command router the built-in strip and the keyboard layer use (group-tab-jump / remove-from-group / ungroup), so capability gating and observability stay uniform.
Signature:
interface TilingRenderTileGroupContext
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (memberNumber: number) => void | Activate the member at | |
| string | The group node id. | |
| ReadonlyArray<TilingGroupMemberView> | The group's members in member order (see TilingGroupMemberView). | |
| (leafId: string) => void | Eject the member with | |
| () => void | Dissolve the whole group into loose panes — dispatches |
TilingResizeCapability
typeResize capability across the two divider orientations.
RESIZE AXIS CONVENTION (read carefully — the divider orientation and the resize axis are deliberately the opposite words):
- A **vertical divider** sits between **side-by-side** panes. Dragging it changes their **widths** → this is a **horizontal** resize (x-axis). In the layout tree this is a split whose
axisis"horizontal"(aflex-rowcontainer with acursor-col-resizehandle). - A **horizontal divider** sits between **stacked** panes. Dragging it changes their **heights** → this is a **vertical** resize (y-axis). In the layout tree this is a split whoseaxisis"vertical"(aflex-colcontainer with acursor-row-resizehandle).
The resize-capability token therefore matches the split axis token directly: "horizontal" gates width dividers, "vertical" gates height dividers.
"both"— every divider is resizable (this is the "entire" option). Default. -"horizontal"— only width dividers (side-by-side panes) are resizable. -"vertical"— only height dividers (stacked panes) are resizable. -"none"— no divider is resizable.
Signature:
type TilingResizeCapability = "both" | "horizontal" | "vertical" | "none";
TilingSlotCommitmentCapability
interfaceLive-drag slot-commitment configuration. mode selects the re-resolution policy; reresolveDeltaPx is the delta-responsive movement threshold (CSS px) — a coarse "should I re-aim" gate distinct from the fine 6px geometric zone hysteresis, so the two never double-count.
Signature:
interface TilingSlotCommitmentCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Re-resolution policy after the ghost seats in a slot. Default | |||
number | (Optional) |
TilingSlotCommitmentMode
typeLive-drag slot re-resolution / commitment policy after the single ghost hops INTO and FILLS a resolved slot (see _agent/single-instance-hop-in-drag-design.md §8):
"zone-exit-hold"— anchored / sticky: the seated slot is pinned through small cursor movements and re-resolves only when the cursor crosses OUT of the seated target's hit footprint (high hysteresis). -"delta-responsive"— DEFAULT: the seated slot re-resolves eagerly once the cursor travels beyondreresolveDeltaPxfrom the seat anchor (or exits the footprint), so the user can re-aim without fully exiting the pane.
Only meaningful in dragMode: "live".
Signature:
type TilingSlotCommitmentMode = "zone-exit-hold" | "delta-responsive";
TilingSplitAxis
typeOrientation of a binary split. "horizontal" places its two children side-by-side (a flex-row, resized by a width divider); "vertical" stacks them (a flex-col, resized by a height divider).
Signature:
type TilingSplitAxis = "horizontal" | "vertical";
TilingThemeDividerTokens
interfaceSplit-divider / gap handle chrome across visible + hidden states.
Signature:
interface TilingThemeDividerTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Structural base incl. focus-visible ring color. | |
| string | Hidden handle (no chrome, hit-area only). | |
| string | Visible + resizable handle (resting + hover). | |
| string | Visible but resize-disabled handle. |
TilingThemeGhostTokens
interfaceDrag-ghost shell — the lifted, portaled copy of the dragged pane.
Signature:
interface TilingThemeGhostTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Ghost body text color. | |
| string | Ghost header bar. | |
| string | Ghost subtitle text color. | |
| string | Ghost article shell (a touch more opaque + deeper shadow than a pane). |
TilingThemePaneHeaderTokens
interfacePane header chrome — resting + focused + the per-pane control buttons.
Signature:
interface TilingThemePaneHeaderTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Resting header bar: border-b, bg, inset sheen. | |
| string | Active/pressed header control button. | |
| string | Resting header control button (maximize etc.). | |
| string | Additive classes when the pane is focused. | |
| string | Additive header classes when the pane is part of the Alt/Opt+click multi-selection set. Deliberately NEUTRAL (no accent) so it never collides with the accent focus frame — multi-selection and focus are orthogonal states a pane can hold simultaneously. | |
| string | The small neutral "selected" check affordance rendered in the header of a multi-selected pane. Neutral tone, distinct from any accent control. | |
| string | Pane title base typography (accent color applied separately). |
TilingThemePaneShellTokens
interfacePane host shell surfaces + interaction-state rings.
Signature:
interface TilingThemePaneShellTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Pane body scroll region text color/leading. | |
| string | Opacity applied to the drag-source pane while it is picked up. | |
| string | Ring on an invalid drop target. | |
| string | Pane subtitle text color. | |
| string | Pane article shell: bg/gradient, radius, shadow/rim, backdrop-filter. |
TilingThemeRootTokens
interfaceRenderer-root + viewport surfaces.
Signature:
interface TilingThemeRootTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Outer renderer container: bg/gradient, radius, padding, outline. | |
| string | Inner viewport (where the pane tree lays out): bg + radius. |
TilingThemeTopBarTokens
interfaceTop-bar / tab-strip chrome.
Signature:
interface TilingThemeTopBarTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Tab-strip container: border, bg, shadow, backdrop. | |
| string | Switcher-control group wrapper (theme picker etc.). | |
| string | Accent-picker group wrapper. | |
| string | Centered pane-switcher overlay card. | |
| string | Switcher card when the pane is NOT selected. | |
| string | Tab chip base typography/layout. | |
| string | Inactive tab chip. | |
| string | Strip title text. |
TilingTileAccentSwatch
interfaceA pickable accent paired with a human label and a solid Tailwind background class for rendering a swatch dot — the generic metadata a palette control (e.g. the showcase top-bar picker) iterates to offer accent selection.
Signature:
interface TilingTileAccentSwatch
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The accent this swatch selects. | |||
string | Human-readable label for the swatch (e.g. in a picker). | ||
string | Tailwind background class rendering the solid swatch dot. |
TilingTitleBarSizingMode
typeThe four title-bar sizing actions a pane offers for ITSELF (no target-pane indirection — the pane the control lives in is the target):
"flexible"— clear any pinned dimension and return the pane to ratio distribution (FLEX). -"static-height"— freeze the pane's height to its measured bbox px. -"static-width"— freeze the pane's width to its measured bbox px. -"static-both"— freeze both dimensions to the measured bbox.
Signature:
type TilingTitleBarSizingMode = "flexible" | "static-height" | "static-width" | "static-both";
TilingTouchDragCapability
interfaceTouch-drag hardening configuration. The drag FSM runs on Pointer Events, so it already covers touch uniformly with mouse/pen — this capability tunes the touch-only choreography that must differ from mouse:
enable— whenfalse, a touch press on the drag handle never starts a drag (touch is reserved for tap/scroll); mouse/pen drag is unaffected. Defaulttrue. -longPressMs— how long a finger must be held before the press becomes a drag pickup (the tap/scroll-vs-drag disambiguator). A pre-long-press scroll-axis flick releases to the page; mouse/pen skip this delay entirely. Default220.
Signature:
interface TilingTouchDragCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Allow touch pointers to start a drag. Default | ||
number | (Optional) Long-press delay (ms) before a held touch becomes a drag. Default |