consumer documentation

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>
      )}
    />
  );
}
live resultdrag · resize · group
HYPR TILING
Editor
Preview
golden path

Quickstart

Three steps to the layout above. Every step is runnable; there are no concept detours here — those come after, only as needed.

1

Install

Add the package and its React 19 peers.

pnpm add @n-uf/hypr-tiling react react-dom
2

Add 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}",
  ],
};
3

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.

recipes

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} />}
    />
  );
}
live resultdrag · resize · group
HYPR TILING
Revenue
$1.2M
Activity
3 new events

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} />
      }
    />
  );
}
live resultdrag · resize · group
HYPR TILING
editor
main.tsx
preview
rendered output

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>
  );
}
live resultdrag · resize · group
HYPR TILING
Signals · theme: neon-terminal
Alerts · theme: neon-terminal

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>
      )}
    />
  );
}
live resultdrag · resize · group
Board
Detail

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.

Related in the reference

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>
  );
}
live resultdrag · resize · group
HYPR TILING
Left
Right

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.

Related in the reference

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>
  );
}
live resultdrag · resize · group
HYPR TILING
One
Two

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>
  );
}
live resultdrag · resize · group
HYPR TILING
A
B
C

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.

mental model

Concepts

Three ideas unblock everything above. That is deliberately all — there is no architecture here.

The layout is a tree you ownA layout is a recursive tree of three node kinds: 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.
The renderer runs interactions; you own the treeTilingRenderer 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).
Interactions are capabilities, on by defaultEvery interaction is enabled unless you narrow it. The single 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.
copy wholesale

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.

live resultdrag · resize · group
HYPR TILING
Revenue
MRR
$48.2k
Active users
DAU
12,904
Error rate
5xx / min
0.03%
Latency
p95
142 ms
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.

live resultdrag · resize · group
HYPR TILING
zsh
$ pnpm dev
▸ ready in 312ms
$ _
logs
[info] listening :3000
[info] compiled ok
htop
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}
    />
  );
}
reference · for when you already know the name

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.

reference · renderer & tiles

Renderer & tiles

The TilingRenderer component, its props, and the tile + custom render-prop contract.

TilingRenderer

variable

The 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

interface

Props 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

className?

string

(Optional) Optional class name applied to the tiling root element.

config

TilingLayoutConfig

Global geometry configuration (gap, min pane size, handle size).

dragAnimationEnabled?

boolean

(Optional) Master gate for all drag-motion choreography. When false, the ghost hop, survivor reflow, pickup-entrance, and swap dip collapse to instant placement (durations → ~0). Default true. The per-knob values (speeds, bounce) are preserved and re-apply when re-enabled.

dragHopEasing?

string

(Optional) CSS <easing-function> for the dragged GHOST's hop transit (hop-in / hop-out / pickup entrance) and the swap/edge-insert ghost motion. Undefined → the default snappy decel cubic-bezier(0.2, 0.8, 0.2, 1). An invalid / empty string falls back to that default (never reaches the compositor as a broken transition). The seated-ghost magnetic linear() curve and the swap-bounce curve are NOT replaced by this knob (sampled non-bezier curves).

dragReflowEasing?

string

(Optional) CSS <easing-function> for the affected ("survivor") panes' FLIP reflow settle. Undefined → defaults to the same curve as dragHopEasing so the ghost and survivors read as one coordinated motion. Invalid / empty falls back to the default.

focusedLeafId?

string | null

(Optional) Controlled focused-leaf id. undefined → uncontrolled; null → nothing focused.

ghostTransitSpeedPercent?

number

(Optional) Speed of the dragged GHOST's transit animation (hop-in / hop-out / pickup entrance) as a percent of the 170ms baseline (100 = baseline). Lower is slower; higher is faster. Clamped to [10, 400]. Default 100.

interaction?

TilingInteractionCapabilities

(Optional) Reactive interaction-capability flags. Undefined resolves to all-enabled. Changing this prop at runtime updates renderer behavior immediately.

layout

TilingLayoutNode

The controlled layout tree to render. Apply every reported edit back here.

maximizedLeafId?

string | null

(Optional) Controlled maximized-pane id. undefined → uncontrolled (renderer-managed internal state). null → controlled, nothing maximized. A leaf id → controlled, that pane maximized. Reacts to prop changes without remount.

onFocusedLeafChange?

(leafId: string) => void

(Optional) Notified whenever the focused leaf changes.

onLayoutChange

(layout: TilingLayoutNode) => void

Called with the next layout tree whenever the renderer edits it (controlled).

onMaximizedLeafChange?

(leafId: string | null) => void

(Optional) Notified whenever the maximized pane changes (null on restore).

onThemeChange?

(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 themeId state (controlled). Omit to hide the switcher — the theme stays whatever themeId (or the theme prop) resolves to. Generic mechanism; the demo theme composition lives with the consumer. BUILT-INS ONLY: the switcher enumerates the built-in registry and reports TilingThemeId members — a consumer running a custom theme object does not wire this callback (its theme never appears in the switcher).

onTileAccentChange?

(tileId: string, accent: TilingTileAccent) => void

(Optional) When provided, the top-bar tab strip surfaces an accent palette picker (the enumerable TILING_TILE_ACCENTS swatches) that recolors the currently-focused pane's tile. The renderer resolves the focused tile id; the consumer owns the tile registry and applies the new accent (e.g. by updating its tiles state). Omit to hide the picker — accent remains a static per-tile property. Generic mechanism; the demo palette composition lives with the consumer.

projectedOverlayBackgroundAlpha?

number

(Optional) Background opacity [0, 1] for the projected landing overlays.

renderTile?

(args: TilingRenderTileProps) => React.ReactNode

(Optional) Custom pane renderer. Receives TilingRenderTileProps; omit to use the default tile.

showDropBorderHints?

boolean

(Optional) Whether drop border hints are painted on candidate panes during a drag.

showDropPreviewOverlays?

boolean

(Optional) Whether translucent projected landing overlays are shown during a drag.

survivorReflowSpeedPercent?

number

(Optional) Speed of the affected ("survivor") panes' REFLOW transform animation as a percent of the 170ms baseline (100 = baseline). Lower is slower; higher is faster. Clamped to [10, 400]. Default 100. When this equals ghostTransitSpeedPercent the two parties are at PARITY, which the coherent non-intersecting transit dip requires (see coherentTransit).

swapBounceMagnitudePercent?

number

(Optional) Swap-landing bounce magnitude as a percent of full overshoot (0 = no overshoot, today's monotonic settle; 100 = pronounced bounce). Applies an easeOutBack overshoot to the ghost seated hop-in and the survivor reflow settle. Per-element (no cross-element coupling), so it is NOT gated by speed parity. Inert while the coherent-transit dip owns the landing. Clamped to [0, 100]. Default 0. Skipped under prefers-reduced-motion.

theme?

TilingTheme

(Optional) Full consumer-authored theme; takes precedence over themeId. Every renderer-painted surface (pane shells, header, focus frame, ghost, dividers, root, tab strip) and every accent-composition resolver comes from this object, so a consumer owns the complete chrome without touching library internals. Undefined → the built-in theme selected by themeId.

themeId?

TilingThemeId

(Optional) Active visual theme id. Selects which built-in TilingTheme paints every renderer surface (pane shells, header, focus frame, ghost, dividers, root, tab strip) and how per-pane accents compose with it. Undefined resolves to the library default ("neon-terminal"). Reacts to prop changes without remount — a live switch re-themes the whole tree.

tiles

ReadonlyArray<TilingTile> | ReadonlyMap<string, TilingTile>

Tile registry, accepted as either an ordered array (resolved by id) or a Map keyed by tile id. A dashboard can pass a plain ReadonlyArray of { id, title, content } tiles; the interactive lab passes a Map.

TilingRenderTileProps

interface

The 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

canGroupMultiSelection

boolean

Whether the current multi-selection (≥2 panes) can be folded into one flat group right now — i.e. group-leaves would change the layout. Any mix of loose panes and/or existing-group members flattens into ONE group (groups the selection touches are dissolved and their members folded in). Drives whether the Group control is offered; false hides it rather than offering a no-op (e.g. the selection is already exactly one group).

dropZone

TilingLeafDropZone | null

The resolved drop zone under the cursor for this pane, or null.

group

readonly

TilingRenderTileGroupContext | null

Group context when this pane is a tabbed group's ACTIVE member (the only member that renders, per the stacking contract); null for loose leaves and on the "drag-ghost" / "drag-cancel" surfaces. See TilingRenderTileGroupContext.

heightSizingMode

TilingPaneSizingMode

This pane's current HEIGHT sizing mode (static/flexible) — drives the active control state.

isDragSource

boolean

Whether this pane is the source of the in-flight drag.

isDropEligible

boolean

Whether this pane is a valid drop destination for the current drag.

isDropTarget

boolean

Whether this pane is the resolved drop target of the in-flight drag.

isFocused

boolean

Whether this pane currently holds focus.

isHoveringDropCandidate

boolean

Whether the cursor is currently hovering this pane as a drop candidate.

isInvalidDrop

boolean

Whether the current hover over this pane would be an invalid drop.

isMaximized

boolean

Whether this pane is currently maximized (fills the tiling viewport).

isMaximizeEnabled

boolean

Whether the maximize capability is enabled (controls header button visibility).

isMoveSource

boolean

Whether this pane is the SOURCE of an in-flight keyboard move (the keyboard analog of a drag source). Drives the "MOVING" affordance.

isMultiSelected

boolean

Whether THIS pane is currently a member of the multi-selection set.

isMultiSelectGroupingEnabled

boolean

Whether the Alt/Opt+click header multi-selection feature is live (paneSwitching.multiSelectGrouping AND the grouping capability). When false, the header click handler must treat a modified click as a plain click and never render the Group control.

isPaneContentVisible

boolean

Global pane-content visibility toggle from the tab strip checkbox. false hides pane body content by default.

isRearrangeEnabled

boolean

Whether drag-to-rearrange is enabled (drives handle affordance).

isTitleBarAcquireSpaceEnabled

boolean

Whether the per-pane title-bar directional acquire-space controls (→ ← ↑ ↓) are enabled.

isTitleBarSizingEnabled

boolean

Whether the per-pane title-bar sizing control (FLEX / STATIC H / STATIC W / BOTH) is enabled.

leafId

string

The leaf node id this render targets.

moveTargetPlacement

TilingMovePlacement | null

When this pane is the pending move-mode DESTINATION, the edge of this pane the moved source will land on (insertLeafAdjacent placement). null when this pane is not the current move-mode target.

onAcquireSpace

(direction: TilingFocusDirection) => void

Grow THIS pane to claim the maximum available space in direction by pushing matching-axis ancestor dividers toward the limit (siblings clamped to their minimum). Emits via onLayoutChange (controlled).

onFocus

(event?: React.SyntheticEvent<HTMLElement>) => void

Establish single focus on this pane (and clear any in-progress multi-selection). Wire to the pane root's onFocus. The renderer reads the focus event's target: when a multi-selection is active and focus landed on a header CONTROL button (Group / maximize), it is a no-op, so a click on the Group button is not nullified by the button stealing focus first.

onGroupMultiSelection

(clickedLeafId: string) => void

Group the current multi-selection into ONE flat tabbed group occupying the CLICKED pane's slot (pass this pane's leafId), then clear the selection. The clicked pane becomes the host (active tab); any existing group in the selection is dissolved and folded in. Wire to the Group control.

onHandlePointerDown

(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 onPointerDown; the renderer arms the drag FSM, crosses the pickup threshold, and takes pointer capture on a stable element so reflow moving panes under the cursor can never lose or hijack the drag. Replaces the former HTML5 draggable + onDragStart/onDragEnd plumbing.

onPointerLeave

(event: React.PointerEvent<HTMLElement>) => void

Clears this pane's hover telemetry when the pointer leaves it.

onPointerMove

(event: React.PointerEvent<HTMLElement>) => void

Pre-drag hover telemetry (drop-intent hit-log); inert while a drag is in flight.

onSetSizingMode

(mode: TilingTitleBarSizingMode) => void

Set THIS pane's sizing mode. STATIC modes measure the pane's current bbox (getBoundingClientRect on its [data-leaf-id] element) and pin the chosen dimension(s) to that pixel value; FLEX clears the pin and returns the pane to ratio distribution. Emits via onLayoutChange (controlled).

onToggleMaximize

() => void

Toggle maximize/restore for this pane.

onToggleMultiSelect

() => 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.

paneBodyRenderMode

TilingPaneBodyRenderMode

Canonical pane-body visibility decision resolved by the renderer policy. Keeps custom tile renderers aligned with the default drag/hidden semantics.

paneOrdinal

number

1-based pane ordinal in current tab order (for generic pane labels).

paneWidthPx

number

Current pane viewport width in pixels (for responsive header/control density).

preview

TilingLeafDropPreview | null

The projected landing/result preview for this pane, or null.

surface

readonly

TilingRenderSurface

Which renderer surface these args paint (see TilingRenderSurface). "pane" for the seated in-tree pane; "drag-ghost" for the floating pickup ghost that follows the cursor; "drag-cancel" for the cancel fly-back overlay. On the two drag surfaces every interaction handler is an inert no-op while the capability display flags stay REAL (reflecting the resolved capabilities), so capability-keyed chrome does not vanish mid-drag; a custom pane that wants different drag chrome branches on this discriminator.

tile

TilingTile

The resolved tile payload for this leaf.

widthSizingMode

TilingPaneSizingMode

This pane's current WIDTH sizing mode (static/flexible) — drives the active control state.

TilingTile

interface

Generic 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

accent?

TilingTileAccent

(Optional) Optional per-tile identity accent. Falls back to "cyan" when omitted.

content?

React.ReactNode

(Optional) Rich body a custom renderTile reads (and the drag ghost paints).

description?

string

(Optional) Optional one-line description surfaced by the default tile chrome.

id

string

Stable tile identity; leaf nodes reference a tile by this id.

rows?

ReadonlyArray<string>

(Optional) Optional text rows for the default tile body. Falls back to [].

title

string

Title shown in the pane header / tab.

TilingTileAccent

type

Per-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";
reference · layout & query

Layout & query

The layout tree node kinds you own in state, the config, and queryTilingLayout for reading structure back out.

DEFAULT_TILING_LAYOUT_CONFIG

variable

Recommended 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()

function

Build 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

TilingLayoutNode

Returns:

TilingLayoutQuery

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

interface

A 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

activeMemberId

string

Always one of members[].id — the single rendered/hit-tested member.

id

string

Stable node identity, unique within the layout tree.

kind

"group"

Node discriminant. Always "group".

members

ReadonlyArray<TilingLeafNode>

≥1 member; array order is the tab order.

sizing?

TilingPaneSizing

(Optional) A group can be static like a leaf (per-dimension static/flexible sizing).

TilingLayoutConfig

interface

Global geometry configuration for the renderer (the config prop).

Signature:

interface TilingLayoutConfig 

Properties

Property

Modifiers

Type

Description

gapPx

number

Gap (CSS px) painted in the gutters between panes.

handleSizePx

number

Resize-handle hit-target thickness (CSS px).

minPaneSizePx

number

Minimum pane extent (CSS px) a resize divider will not shrink a pane below.

TilingLayoutNode

type

A 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

interface

A 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

groups

readonly

ReadonlyArray<TilingGroupNode>

Every group in the tree (depth-first, reading order) — the tab-strip source.

hasMasterSplit

readonly

boolean

Whether any split node is in "master" layout mode.

leafIds

readonly

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).

neighborLeafId

readonly

(fromLeafId: string, direction: TilingFocusDirection) => string | null

The leaf id reached from fromLeafId in the given direction (spatial neighbor by pane geometry), or null when there is no pane that way.

splits

readonly

ReadonlyArray<TilingSplitNode>

Every split node in the tree (depth-first, reading order).

tileOrder

readonly

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

interface

A 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

id

string

Stable node identity, unique within the layout tree.

kind

"leaf"

Node discriminant. Always "leaf".

sizing?

TilingPaneSizing

(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible.

tileId

string

The TilingTile.id this leaf renders.

TilingSplitNode

interface

A 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

axis

TilingSplitAxis

Split orientation ("horizontal" = side-by-side, "vertical" = stacked).

first

TilingLayoutNode

The first (top/left) child subtree.

gapPx?

number

(Optional) Optional per-split gap override (CSS px) between the two children.

id

string

Stable node identity, unique within the layout tree.

kind

"split"

Node discriminant. Always "split".

layoutMode?

TilingLayoutMode

(Optional) Arrangement of this subtree's slots. Undefined → "dwindle" (the binary split layout — every hand-authored tree + the test baseline is unchanged). "master" lays the subtree's descendant slots out as master area + stack.

masterCount?

number

(Optional) layoutMode: "master" only — number of slots in the master area. Undefined → 1. Clamped to [1, slotCount] by the resolver / reducers.

masterOrientation?

TilingMasterOrientation

(Optional) layoutMode: "master" only — where the master area sits. Undefined → "left". In master mode the split's ratio is reused as the master-area fraction along this orientation's primary axis.

minPaneSizePx?

number

(Optional) Optional per-split minimum pane extent (CSS px) enforced on resize.

ratio

number

Fraction [0, 1] of the region allotted to first along axis.

second

TilingLayoutNode

The second (bottom/right) child subtree.

sizing?

TilingPaneSizing

(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible.

reference · theming

Theming

Built-in themes, per-pane accents, the theme provider, and useTilingTheme for reading tokens inside a custom pane.

DEFAULT_TILING_THEME_ID

variable

Default library theme id (preserves the prior look at the round-1 checkpoint).

Signature:

DEFAULT_TILING_THEME_ID: TilingThemeId

resolveTilingTheme()

function

Resolve 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:

TilingTheme

TILING_THEMES

variable

Ordered, enumerable theme list — the generic capability a theme switcher iterates.

Signature:

TILING_THEMES: readonly TilingTheme[]

TilingTheme

interface

A 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

divider

readonly

TilingThemeDividerTokens

Split-divider / gap handle tokens.

ghost

readonly

TilingThemeGhostTokens

Drag-ghost shell tokens.

id

readonly

TilingThemeId | (string & {})

The theme's id. A built-in theme uses a TilingThemeId member; a consumer-authored theme mints its own id string (the string & {} widening keeps built-in ids autocompletable while admitting any id). TILING_THEME_REGISTRY stays keyed by the closed TilingThemeId union, so the compile-time built-in coverage guarantee is unaffected.

label

readonly

string

Human-readable theme label (e.g. for a theme picker).

paneHeader

readonly

TilingThemePaneHeaderTokens

Pane header chrome tokens.

paneShell

readonly

TilingThemePaneShellTokens

Pane host shell surface + interaction-state tokens.

resolveAccentText

readonly

(accent: TilingTileAccent | undefined) => string

Accent title-text color.

resolveFocusFrame

readonly

(accent: TilingTileAccent | undefined) => string

Full focused-pane frame (structural border/ring + accent glow).

resolvePaneAccentSurface

readonly

(accent: TilingTileAccent | undefined) => string

Resting pane accent composition (border tint + colored shadow).

resolveTabActive

readonly

(accent: TilingTileAccent | undefined) => string

Active tab / switcher / group-member chip.

root

readonly

TilingThemeRootTokens

Renderer-root + viewport surface tokens.

topBar

readonly

TilingThemeTopBarTokens

Top-bar / tab-strip chrome tokens.

TilingThemeId

type

Closed 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()

function

Provides 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()

function

Reads the active theme from context (defaults to the library default).

Signature:

declare function useTilingTheme(): TilingTheme;

Returns:

TilingTheme

reference · commands

Commands

The imperative command handle and the typed command union you dispatch from your own controls.

TilingCommand

type

The 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

interface

The 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

dispatch

(command: TilingCommand) => void

Route a TilingCommand through the renderer's command router.

reference · advanced helpers

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

variable

Default drag animation speed percent (100 = the baseline duration).

Signature:

DEFAULT_DRAG_ANIMATION_SPEED_PERCENT: number

DEFAULT_DRAG_HOP_EASING

variable

The default ghost hop / pickup / hop-out timing function (a snappy decel).

Signature:

DEFAULT_DRAG_HOP_EASING: string

DEFAULT_DRAG_REFLOW_EASING

variable

The 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

variable

First palette member — the fallback when a tile omits accent.

Signature:

DEFAULT_TILE_ACCENT: TilingTileAccent

DRAG_ANIMATION_SPEED_MAX_PERCENT

variable

Fastest (ceiling) drag animation speed percent the slider + clamp allow.

Signature:

DRAG_ANIMATION_SPEED_MAX_PERCENT: number

DRAG_ANIMATION_SPEED_MIN_PERCENT

variable

Slowest (floor) drag animation speed percent the slider + clamp allow.

Signature:

DRAG_ANIMATION_SPEED_MIN_PERCENT: number

isCommandEnabled()

function

Whether 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

TilingCommand

gates

TilingCommandGates

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()

function

true 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

MultiSelectModifierState

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

interface

The 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

altKey

readonly

boolean

Whether Alt/Opt was held (the multi-select discriminator).

ResolvedTilingDragRecoveryCapability

interface

Fully-resolved drag-recovery configuration (no optional fields).

Signature:

interface ResolvedTilingDragRecoveryCapability 

Properties

Property

Modifiers

Type

Description

enable

boolean

Resolved self-healing recovery enable flag.

frameDeadlineMs

number

Resolved rAF-fallback slack (ms).

maxDraggingIdleMs

number

Resolved idle-watchdog deadline (ms).

transitionSlackMs

number

Resolved transition-completion slack (ms).

ResolvedTilingDropHitZoneGeometryCapability

interface

Fully-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

centerMinPx

number

Resolved floor (CSS px) for the center rectangle extent.

centerRatio

number

Resolved symmetric/representative center swap-zone fraction.

centerRatioX

number

Resolved per-axis HORIZONTAL swap-zone fraction the resolver consumes.

centerRatioY

number

Resolved per-axis VERTICAL swap-zone fraction the resolver consumes.

hysteresisPx

number

Resolved boundary stickiness (pane-local CSS px).

ResolvedTilingGroupingCapability

interface

Resolved group / tabbed-stacking capability (no optional fields).

Signature:

interface ResolvedTilingGroupingCapability 

Properties

Property

Modifiers

Type

Description

enable

boolean

Whether group / tabbed-stacking is enabled.

showGroupTabStrip

boolean

Whether the per-group tab strip renders.

ResolvedTilingInteractionCapabilities

interface

Fully-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

coherentTransit

boolean

Whether coherent non-intersecting transit is enabled.

customCursor

boolean

Whether the custom-rendered drag cursor is used during live drag.

dragMode

TilingDragMode

Resolved drag feedback mode ("preview" vs "live").

dragRecovery

ResolvedTilingDragRecoveryCapability

Resolved drag/transition self-healing recovery configuration.

dropHitZoneGeometry

ResolvedTilingDropHitZoneGeometryCapability

Resolved adjustable drop hit-zone geometry.

focus

boolean

Whether pane focus selection is enabled.

ghostPickupScalePercent

number

Resolved ghost pickup scale as a percent of the source pane bbox.

grouping

ResolvedTilingGroupingCapability

Resolved group / tabbed-stacking capability.

keyBindings

ResolvedTilingKeyBindings

Resolved consumer key bindings (the raw registry passed through; an empty binding list + replaceDefaults: false when omitted). The renderer matches these before the default keymap path.

keymap

ResolvedTilingKeymap

Resolved keymap (every chord explicit).

masterLayout

boolean

Whether the master/stack layout engine is enabled.

maximize

ResolvedTilingMaximizeCapability

Resolved maximize-to-viewport capability.

paneSwitching

ResolvedTilingPaneSwitchingCapability

Resolved tab-like pane-switching capability.

paneTitleBarControls

ResolvedTilingPaneTitleBarControlsCapability

Resolved per-pane title-bar controls capability.

rearrange

boolean

Whether drag-to-rearrange is enabled.

resize

TilingResizeCapability

Resolved divider resize capability.

resizeHandlesVisible

boolean

Whether split-divider resize handles are visibly rendered.

slotCommitment

ResolvedTilingSlotCommitmentCapability

Resolved live-drag slot-commitment configuration.

slotHopInEnabled

boolean

Whether the single ghost hops into and fills the resolved slot.

touchDrag

ResolvedTilingTouchDragCapability

Resolved touch-drag hardening configuration.

ResolvedTilingKeyBindings

interface

Fully-resolved key-binding registry (no optional fields).

Signature:

interface ResolvedTilingKeyBindings 

Properties

Property

Modifiers

Type

Description

bindings

ReadonlyArray<TilingKeyBinding>

Resolved consumer chord→command bindings (empty when none supplied).

replaceDefaults

boolean

Whether the default keymap bindings are suppressed.

ResolvedTilingKeyChord

interface

Fully-resolved key chord (every modifier explicit). Matches KeyboardEvent.code.

Signature:

interface ResolvedTilingKeyChord 

Properties

Property

Modifiers

Type

Description

alt

boolean

Whether the Alt key must be held.

code

string

Physical KeyboardEvent.code this chord matches.

ctrl

boolean

Whether the Ctrl key must be held.

meta

boolean

Whether the Meta (Cmd / Win) key must be held.

shift

boolean

Whether the Shift key must be held.

ResolvedTilingKeyChordModifiers

interface

Fully-resolved jump-to-pane modifier set.

Signature:

interface ResolvedTilingKeyChordModifiers 

Properties

Property

Modifiers

Type

Description

alt

boolean

Whether the Alt key must be held.

ctrl

boolean

Whether the Ctrl key must be held.

meta

boolean

Whether the Meta (Cmd / Win) key must be held.

shift

boolean

Whether the Shift key must be held.

ResolvedTilingKeymap

interface

Fully-resolved keymap (no optional fields).

Signature:

interface ResolvedTilingKeymap 

Properties

Property

Modifiers

Type

Description

cycleLayoutMode

ResolvedTilingKeyChord

Resolved chord to toggle the focused subtree dwindle ⇄ master.

cycleMasterOrientation

ResolvedTilingKeyChord

Resolved chord to cycle the master-area orientation.

decrementMasterCount

ResolvedTilingKeyChord

Resolved chord to remove one tile from the master area.

decrementMasterRatio

ResolvedTilingKeyChord

Resolved chord to shrink the master-area fraction.

enterMoveMode

ResolvedTilingKeyChord

Resolved chord to enter keyboard move mode.

focusCurrentOrLast

ResolvedTilingKeyChord

Resolved chord for the MRU focus current-or-last toggle.

focusDown

ResolvedTilingKeyChord

Resolved chord to move focus to the neighbor BELOW.

focusLeft

ResolvedTilingKeyChord

Resolved chord to move focus to the LEFT neighbor.

focusRight

ResolvedTilingKeyChord

Resolved chord to move focus to the RIGHT neighbor.

focusUp

ResolvedTilingKeyChord

Resolved chord to move focus to the neighbor ABOVE.

groupTabNext

ResolvedTilingKeyChord

Resolved chord to activate the next member tab of the focused group.

groupTabPrevious

ResolvedTilingKeyChord

Resolved chord to activate the previous member tab of the focused group.

incrementMasterCount

ResolvedTilingKeyChord

Resolved chord to add one tile to the master area.

incrementMasterRatio

ResolvedTilingKeyChord

Resolved chord to grow the master-area fraction.

jumpToPane

ResolvedTilingKeyChordModifiers

Resolved modifier set for the Alt+1..Alt+9 jump family.

nextPane

ResolvedTilingKeyChord

Resolved chord to cycle to the next pane.

previousPane

ResolvedTilingKeyChord

Resolved chord to cycle to the previous pane.

restore

ResolvedTilingKeyChord

Resolved chord to restore from maximize.

toggleGroup

ResolvedTilingKeyChord

Resolved chord to group/ungroup the focused pane.

toggleMaximize

ResolvedTilingKeyChord

Resolved chord to toggle maximize on the focused pane.

ResolvedTilingMaximizeCapability

interface

Resolved maximize capability (no optional fields).

Signature:

interface ResolvedTilingMaximizeCapability 

Properties

Property

Modifiers

Type

Description

enable

boolean

Whether the maximize/restore control + shortcuts are live.

ResolvedTilingPaneSwitchingCapability

interface

Resolved pane-switching capability (no optional fields).

Signature:

interface ResolvedTilingPaneSwitchingCapability 

Properties

Property

Modifiers

Type

Description

enable

boolean

Whether pane switching (tab strip + cycle/jump shortcuts) is live.

multiSelectGrouping

boolean

Whether Alt/Opt+click header multi-selection grouping is live.

showContentToggle

boolean

Whether the tab strip's pane-content visibility checkbox renders.

showSwitcherOverlay

boolean

Whether the Cmd+Tab-style switcher overlay renders while cycling.

showTabStrip

boolean

Whether the tab strip renders.

tabDoubleClickMaximize

boolean

Whether double-clicking a tab toggles that pane's maximize state.

ResolvedTilingPaneTitleBarControlsCapability

interface

Resolved per-pane title-bar control capability (no optional fields).

Signature:

interface ResolvedTilingPaneTitleBarControlsCapability 

Properties

Property

Modifiers

Type

Description

acquireSpace

boolean

Whether the per-pane directional acquire-space controls render.

sizing

boolean

Whether the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control renders.

ResolvedTilingSlotCommitmentCapability

interface

Fully-resolved slot-commitment configuration (no optional fields).

Signature:

interface ResolvedTilingSlotCommitmentCapability 

Properties

Property

Modifiers

Type

Description

mode

TilingSlotCommitmentMode

Resolved re-resolution policy after the ghost seats in a slot.

reresolveDeltaPx

number

Resolved delta-responsive re-aim threshold in CSS px.

ResolvedTilingTouchDragCapability

interface

Fully-resolved touch-drag configuration (no optional fields).

Signature:

interface ResolvedTilingTouchDragCapability 

Properties

Property

Modifiers

Type

Description

enable

boolean

Resolved touch-drag enable flag.

longPressMs

number

Resolved long-press delay (ms) before a held touch becomes a drag.

resolveInteractionCapabilities()

function

Single 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

TilingInteractionCapabilities | null

(Optional)

Returns:

ResolvedTilingInteractionCapabilities

resolveJumpedPaneId()

function

Resolve 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

variable

The 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

variable

Interaction 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

variable

All-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

variable

Built-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

variable

Picker-ready metadata (accent + label + swatch class) for every accent.

Signature:

TILING_TILE_ACCENT_SWATCHES: readonly TilingTileAccentSwatch[]

TILING_TILE_ACCENTS

variable

Ordered, enumerable accent palette — the generic capability a consumer iterates to build an accent picker.

Signature:

TILING_TILE_ACCENTS: readonly TilingTileAccent[]

TilingAccentHue

interface

Per-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

focusBorder

readonly

string

Focused-pane border color.

focusGlow

readonly

string

Full-intensity neon focus glow (box-shadow).

focusGlowSoft

readonly

string

Dialed-back focus glow for refined / calm themes (box-shadow).

focusRing

readonly

string

Focused-pane focus-ring color.

label

readonly

string

Human-facing palette label (also the picker swatch label).

surfaceBorder

readonly

string

Resting pane border tint (low alpha).

surfaceShadow

readonly

string

Resting pane colored drop-shadow tint.

swatch

readonly

string

Solid background for a palette swatch dot.

tabBg

readonly

string

Active tab/switcher chip translucent fill.

tabBgSoft

readonly

string

Subtle active-tab fill for low-contrast themes.

tabBorder

readonly

string

Active tab/switcher chip border.

text

readonly

string

Accent title / metadata text.

textStrong

readonly

string

Strong accent text used on active chips.

TilingCommandGates

interface

The 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

acquireSpaceEnabled

boolean

Directional acquire-space command.

focusEnabled

boolean

Focus selection commands.

groupingEnabled

boolean

Group / tabbed-stacking commands (HT-GROUP-TABBED-STACKING).

layoutEnabled

boolean

Master/stack layout-mode commands (HT-LAYOUT-MASTER-STACK).

maximizeEnabled

boolean

Maximize / restore commands.

paneSwitchingEnabled

boolean

Pane-switching (focus-cycle / focus-jump) commands.

rearrangeEnabled

boolean

Drag/keyboard rearrange (move / swap / insert) commands.

resizeEnabled

boolean

Divider resize (set-split-ratio / toggle-split-axis) commands.

sizingEnabled

boolean

Per-pane title-bar sizing (set-sizing) command.

TilingDragHandle()

function

The 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

TilingDragHandleProps

Returns:

React.ReactElement

TilingDragHandleProps

interface

Props for TilingDragHandle().

Signature:

interface TilingDragHandleProps extends Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick"> 

Extends: Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick">

Properties

Property

Modifiers

Type

Description

pane

Pick<TilingRenderTileProps, "onHandlePointerDown" | "isMultiSelectGroupingEnabled" | "onToggleMultiSelect">

The renderTile args for this pane (the drag-pickup handler and the multi-select toggle handlers are read). Pass the whole args object.

TilingDragMode

type

Drag-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

interface

Drag / 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). Default true. - maxDraggingIdleMs — the drag idle-watchdog deadline: armed / dragging with no POINTER_MOVE / TARGET_RESOLVED progress for longer than this (measured MONOTONICALLY, so throttle-robust) force-reconciles to idle via the existing POINTER_CANCEL edge with pointer capture released. Default 30 × BASELINE_DRAG_HOP_DURATION_MS ≈ 5100ms. - frameDeadlineMs — the rAF-fallback slack: each FLIP "play-to-identity" write is armed as a requestAnimationFrame racing a setTimeout of this long, first-wins + idempotent, so a starved frame never leaves an element frozen at its inverted First. Default ~2 frames ≈ 32ms. - transitionSlackMs — the transition-completion slack: style cleanup fires on transitionend OR duration + transitionSlackMs, whichever first. Default 60ms (names the existing +60 mask-close slack).

Signature:

interface TilingDragRecoveryCapability 

Properties

Property

Modifiers

Type

Description

enable?

boolean

(Optional) Enable the self-healing recovery layer. Default true.

frameDeadlineMs?

number

(Optional) rAF-fallback slack (ms). Default ~2 frames ≈ 32.

maxDraggingIdleMs?

number

(Optional) Idle-watchdog deadline (ms). Default 30 × BASELINE_DRAG_HOP_DURATION_MS ≈ 5100.

transitionSlackMs?

number

(Optional) Transition-completion slack (ms). Default 60.

TilingDropAction

type

The 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

interface

Adjustable 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

centerMinPx?

number

(Optional) Floor (CSS px) for the center rectangle's extent on each axis so tiny panes keep a usable swap target even when width * centerRatio would collapse it. Default 24.

centerRatio?

number

(Optional) Fraction of each pane axis spanned by the central SWAP rectangle. The directional INSERT edge band depth is the complement, (1 - centerRatio) / 2. Clamped to [0.05, 0.95] by the resolver. Default 0.34. Acts as the SYMMETRIC convenience: it sets BOTH axes unless a per-axis override (centerRatioX / centerRatioY) is supplied for that axis.

centerRatioX?

number

(Optional) Per-axis HORIZONTAL (width) swap-zone fraction override. When set, it sizes the center rectangle's X extent independently of centerRatio, letting a non-square pane carry an axis-specific swap-zone proportion. Falls back to centerRatio then the default 0.34. Clamped to [0.05, 0.95].

centerRatioY?

number

(Optional) Per-axis VERTICAL (height) swap-zone fraction override. When set, it sizes the center rectangle's Y extent independently of centerRatio. Falls back to centerRatio then the default 0.34. Clamped to [0.05, 0.95].

hysteresisPx?

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. 0 disables hysteresis. Default 6.

TilingFocusDirection

type

A directional focus / move target relative to the current pane.

Signature:

type TilingFocusDirection = "left" | "right" | "up" | "down";

TilingGroupingCapability

interface

Group / 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

enable?

boolean

(Optional) Enable grouping. When true (default), the grouping commands (toggle-group, ungroup, add-to-group, group-tab-cycle, group-tab-jump, group-leaves) and their default keybindings are live, and drag-onto-a-group's-tab-strip merges into the group. When false, those commands are no-ops and drag-into-group is disabled (an existing group still renders per this capability's showGroupTabStrip).

showGroupTabStrip?

boolean

(Optional) Render the per-group tab strip (the member tabs a tabbed-stacking group paints above its active member). Default true. When false, the strip is suppressed and the group renders only its active member; the keyboard group commands (group-tab-cycle, group-tab-jump, remove-from-group, ungroup) keep working, so a consumer that hides the strip can paint its own group chrome and route it through the same commands. Note: drag-onto-the-strip group merge requires a mounted strip, so hiding the strip also removes that drop target. Distinct from paneSwitching.showTabStrip, which governs the TOP-LEVEL tab strip only.

TilingGroupMemberView

interface

One 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

isActive

readonly

boolean

Whether this member is the group's active (rendered) member.

leafId

readonly

string

The member's leaf node id.

memberNumber

readonly

number

1-based position in the group's member order — the operand TilingRenderTileGroupContext.activateMember takes (and the number the built-in strip / keyboard Alt+<n> jump display).

tile

readonly

TilingTile | null

The resolved tile payload, or null when the tile map has no match.

tileId

readonly

string

The tile id the member's leaf points at.

TilingInteractionCapabilities

interface

Public, 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

coherentTransit?

boolean

(Optional) Coherent non-intersecting transit. When true (default), the moving source ghost and the displaced target never visually OVERLAP mid-transition: on a SWAP both moving boxes scale down toward ~70% mid-transit (best-effort) then scale back into place, coordinated with the survivor reflow so even if their paths cross the shrunk boxes do not collide. Trivial / no-op for edge-insert (the boxes never trade places). Only meaningful in dragMode: "live"; skipped under prefers-reduced-motion.

customCursor?

boolean

(Optional) Custom-rendered drag cursor (interaction tier "c"). When true (default), an active live drag hides the OS cursor (cursor: none on the tiling root) and renders a transform-pinned cursor element that follows the pointer and reflects the drag FSM + drop validity (neutral grab / directional arrow / swap / blocked). When false, the native OS cursor is used during drag. Only meaningful in dragMode: "live".

dragMode?

TilingDragMode

(Optional) Drag-to-rearrange feedback mode: "preview" (non-committing projected overlays) vs "live" (Hyprland detach: source detached on pickup, frozen tree, cursor-following ghost, commit on release). Default "live" (the resolved default in TILING_INTERACTION_CAPABILITY_DEFAULTS). Only meaningful when rearrange is enabled; live mode is also unreachable for static-pane layouts (drag-rearrange is auto-gated off there).

dragRecovery?

TilingDragRecoveryCapability

(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 dragMode: "live". Default all-resolved (enable: true, maxDraggingIdleMs ≈ 5100, frameDeadlineMs ≈ 32, transitionSlackMs: 60).

dropHitZoneGeometry?

TilingDropHitZoneGeometryCapability

(Optional) Adjustable drag-drop hit-zone geometry (center swap fraction, center floor, boundary hysteresis). Undefined → today's TILING_DROP_INTENT_CONFIG defaults. Reactive: changing it at runtime re-shapes the drop zones (and their visual overlay) immediately.

focus?

boolean

(Optional) Pane focus selection. When false, focus selection is suppressed. Default true.

ghostPickupScalePercent?

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 [10, 150]; clamped where consumed. Default 90. Only meaningful in dragMode: "live"; skipped under prefers-reduced-motion.

grouping?

boolean | TilingGroupingCapability

(Optional) Group / tabbed-stacking (HT-GROUP-TABBED-STACKING). A bare boolean is shorthand for { enable }; the object form additionally governs the per-group tab strip via showGroupTabStrip (see TilingGroupingCapability). Default true (grouping enabled, strip rendered).

keyBindings?

TilingKeyBindings

(Optional) Public chord→command binding registry. Consumer bindings augment (and on a chord collision override) the default keymap bindings; set replaceDefaults to drop the built-in keymap path entirely. Undefined → the default keymap bindings only.

keymap?

TilingKeymap

(Optional) Top-level keymap. Capability-level keymap overrides take precedence over this; this takes precedence over the documented defaults.

masterLayout?

boolean

(Optional) Master/stack layout engine (HT-LAYOUT-MASTER-STACK). When true (default), the layout-mode + master commands (cycle-layout-mode, adjust-master-count, cycle-master-orientation, adjust-master-ratio, …) and their default keybindings are live, and a subtree set to layoutMode: "master" renders as a master area + stack. When false, those commands are no-ops (a master-mode tree still renders via the resolver, but the operator cannot change the mode/params). Default true.

maximize?

TilingMaximizeCapability

(Optional) Maximize-to-viewport capability. Default all-enabled.

paneSwitching?

TilingPaneSwitchingCapability

(Optional) Tab-like pane-switching capability. Default all-enabled.

paneTitleBarControls?

TilingPaneTitleBarControlsCapability

(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.

rearrange?

boolean

(Optional) Drag-to-rearrange (move / swap / edge-insert) capability. When false, panes are not draggable and no drop overlays / hit-zones activate. Default true.

resize?

TilingResizeCapability

(Optional) Divider resize capability. Default "both" (every divider resizable).

resizeHandlesVisible?

boolean

(Optional) Whether split-divider resize handles are visibly rendered. false hides handle chrome (separator paint / hover affordance) while preserving the divider hit-target and resize capability gating from resize. Default false.

slotCommitment?

TilingSlotCommitmentCapability

(Optional) Live-drag slot re-resolution / commitment policy (the single ghost hops INTO and FILLS the resolved slot). Only meaningful in dragMode: "live". Default all-resolved (mode: "delta-responsive", reresolveDeltaPx: 24).

slotHopInEnabled?

boolean

(Optional) Live-drag slot hop-in. Selects between the two drag-presentation behaviors for the dragged pane:

  • true (default, ORIGINAL): the single content-honoring ghost HOPS INTO and FILLS the resolved slot — the seat rect is measured so the ghost seats as the single instance and no separate empty reservation lingers beside a free-following ghost. - false (the seat-measurement is deliberately skipped): the ghost free-follows the cursor and the in-tree content-less reservation slot stays shown — the reservation-plus-ghost duality.

Orthogonal to the CONTENT toggle: in BOTH modes the pane body honors content uniformly (see resolvePaneBodyRenderMode). Only meaningful in dragMode: "live". Default true.

touchDrag?

TilingTouchDragCapability

(Optional) Touch-drag hardening (touch enable + long-press disambiguation). Only meaningful when rearrange is enabled. Default all-resolved (enable: true, longPressMs: 220).

TilingKeyBinding

interface

A 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

chord

TilingKeyChord

The chord that triggers this binding (matched on KeyboardEvent.code).

command

TilingCommand

The command dispatched when the chord matches.

TilingKeyBindings

interface

Consumer 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

bindings?

ReadonlyArray<TilingKeyBinding>

(Optional) Consumer chord→command bindings; augment (or override on collision) the defaults.

replaceDefaults?

boolean

(Optional) When true, the default keymap bindings are NOT consulted. Default false (augment).

TilingKeyChord

interface

A 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

alt?

boolean

(Optional) Require the Alt key. Default false.

code

string

Matches KeyboardEvent.code (e.g. "Enter", "Escape", "BracketLeft", "BracketRight", "Digit1").

ctrl?

boolean

(Optional) Require the Ctrl key. Default false.

meta?

boolean

(Optional) Require the Meta (Cmd / Win) key. Default false.

shift?

boolean

(Optional) Require the Shift key. Default false.

TilingKeyChordModifiers

interface

Modifier 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

alt?

boolean

(Optional) Require the Alt key. Default false.

ctrl?

boolean

(Optional) Require the Ctrl key. Default false.

meta?

boolean

(Optional) Require the Meta (Cmd / Win) key. Default false.

shift?

boolean

(Optional) Require the Shift key. Default false.

TilingKeymap

interface

Public, 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

cycleLayoutMode?

TilingKeyChord

(Optional) Toggle the focused subtree's layout mode dwindle ⇄ master (the Hyprland layout-toggle analog). Default Alt+L (code KeyL). Only meaningful when masterLayout is enabled.

cycleMasterOrientation?

TilingKeyChord

(Optional) Cycle the master-area orientation left → top → right → bottom → left. Default Alt+Shift+O (code KeyO + Shift). Bare Alt+O is avoided because Chrome/Edge on Windows/Linux reserve it for the browser menu. Switches to master layout mode automatically when invoked from dwindle.

decrementMasterCount?

TilingKeyChord

(Optional) Remove one tile from the master area (-1 master count). Default Alt+- (code Minus).

decrementMasterRatio?

TilingKeyChord

(Optional) Shrink the master-area fraction (-0.05 ratio). Default Alt+, (code Comma).

enterMoveMode?

TilingKeyChord

(Optional) Enter keyboard MOVE MODE on the focused pane (the keyboard analog of a drag pickup). Default Alt+M (code KeyM). While in move mode the focus arrows pick a destination, Enter commits the relocation (insertLeafAdjacent), and Escape cancels. Only meaningful when rearrange is enabled.

focusCurrentOrLast?

TilingKeyChord

(Optional) Toggle focus between the current pane and the most-recently-focused other pane (the MRU "focus current-or-last" toggle; Hyprland focuscurrentorlast analog). Default Alt+\`` (code Backquote). Only meaningful when focus` is enabled.

focusDown?

TilingKeyChord

(Optional) Move focus to the geometric neighbor BELOW. Default bare ArrowDown.

focusLeft?

TilingKeyChord

(Optional) Move focus to the geometric neighbor on the LEFT. Default bare ArrowLeft.

focusRight?

TilingKeyChord

(Optional) Move focus to the geometric neighbor on the RIGHT. Default bare ArrowRight.

focusUp?

TilingKeyChord

(Optional) Move focus to the geometric neighbor ABOVE. Default bare ArrowUp.

groupTabNext?

TilingKeyChord

(Optional) Activate the next member tab of the focused group. Default Alt+K (code KeyK).

groupTabPrevious?

TilingKeyChord

(Optional) Activate the previous member tab of the focused group. Default Alt+J (code KeyJ).

incrementMasterCount?

TilingKeyChord

(Optional) Add one tile to the master area (+1 master count). Default Alt+= (code Equal).

incrementMasterRatio?

TilingKeyChord

(Optional) Grow the master-area fraction (+0.05 ratio). Default Alt+. (code Period).

jumpToPane?

TilingKeyChordModifiers

(Optional) Modifier set for the Alt+1..Alt+9 jump family. Default Alt.

nextPane?

TilingKeyChord

(Optional) Cycle to the next pane. Default Alt+].

previousPane?

TilingKeyChord

(Optional) Cycle to the previous pane. Default Alt+[.

restore?

TilingKeyChord

(Optional) Restore from maximize. Default Escape.

toggleGroup?

TilingKeyChord

(Optional) Toggle grouping for the focused pane: group it with its reading-order neighbor, or ungroup it if already grouped (the Hyprland togglegroup analog). Default Alt+G (code KeyG). Only meaningful when grouping is enabled.

toggleMaximize?

TilingKeyChord

(Optional) Toggle maximize on the focused pane. Default Alt+Enter (code Enter).

TilingLayoutMode

type

Arrangement of a split subtree's slots:

  • "dwindle" — the default recursive binary-split layout: each split distributes its two children along its axis by ratio (the original and only Phase-1/2 algorithm). - "master" — a master-area + stack layout (the Hyprland master analog). The split's descendant slots (leaves / groups, flattened in reading order) are laid out as masterCount master tiles in a master area plus the remaining tiles in a stack; the split's ratio is reused as the master-area fraction and masterOrientation places 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

interface

A projected landing/result preview shown on a pane during a drag.

Signature:

interface TilingLeafDropPreview 

Properties

Property

Modifiers

Type

Description

mode

TilingLeafPreviewMode

Whether the preview depicts a swap or an edge-insert.

partnerLeafId

string

The other leaf involved (swap counterpart or insert neighbor).

role

TilingLeafPreviewRole

Whether this preview is the drag-source landing or the drop-target result.

zone

TilingLeafDropZone

The drop zone the preview corresponds to.

TilingLeafDropZone

type

The five drop regions of a pane: a central swap zone plus four insert edges.

Signature:

type TilingLeafDropZone = "center" | "left" | "right" | "top" | "bottom";

TilingLeafPreviewMode

type

Whether a preview depicts a swap or an edge-insert result.

Signature:

type TilingLeafPreviewMode = "swap" | "edge-insert";

TilingLeafPreviewRole

type

Which side of a preview a shadow represents (drag source vs drop target).

Signature:

type TilingLeafPreviewRole = "drag-source-landing-shadow" | "drop-target-result-shadow";

TilingMasterOrientation

type

Where 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

interface

Maximize (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

enable?

boolean

(Optional) Enable the per-pane maximize/restore control + shortcuts. Default true.

keymap?

Pick<TilingKeymap, "toggleMaximize" | "restore">

(Optional) Per-capability keybinding overrides for toggleMaximize / restore. These take precedence over the top-level keymap, which takes precedence over the documented defaults.

TilingMovePlacement

type

Which edge of a target pane an inserted/moved pane lands on.

Signature:

type TilingMovePlacement = "left" | "right" | "top" | "bottom";

TilingPaneAction()

function

A 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

TilingPaneActionProps

Returns:

React.ReactElement

TilingPaneActionProps

type

Props for TilingPaneAction().

Signature:

type TilingPaneActionProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

TilingPaneBody()

function

The 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

TilingPaneBodyProps

Returns:

React.ReactElement

TilingPaneBodyProps

interface

Props for TilingPaneBody().

Signature:

interface TilingPaneBodyProps extends React.HTMLAttributes<HTMLDivElement> 

Extends: React.HTMLAttributes<HTMLDivElement>

Properties

Property

Modifiers

Type

Description

pane

Pick<TilingRenderTileProps, "paneBodyRenderMode">

The renderTile args for this pane (only paneBodyRenderMode is read). Pass the whole args object.

TilingPaneBodyRenderMode

type

Resolved 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

type

Pane cycle direction for the reading-order ring (next / previous, wraparound).

Signature:

type TilingPaneCycleDirection = "next" | "previous";

TilingPaneRoot()

function

The 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

TilingPaneRootProps

Returns:

React.ReactElement

TilingPaneRootProps

interface

Props 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

pane

Pick<TilingRenderTileProps, "leafId" | "onFocus" | "onPointerMove" | "onPointerLeave">

The renderTile args for this pane (only its leafId and the focus/hover handlers are read). Pass the whole args object; the primitive wires the root correctly.

TilingPaneSizing

interface

Per-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

height?

TilingPaneSizingMode

(Optional) Sizing mode for the HEIGHT dimension. Undefined → "flexible".

heightPx?

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 height === "static". See widthPx.

width?

TilingPaneSizingMode

(Optional) Sizing mode for the WIDTH dimension. Undefined → "flexible".

widthPx?

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 width === "static". When present, the static-width pane renders at exactly this pixel extent (a measured bbox FREEZE) instead of being content-sized. Undefined → the static dimension is content-sized (the legacy intrinsic behavior).

TilingPaneSizingMode

type

Per-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

interface

Tab-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

enable?

boolean

(Optional) Enable pane switching (tab strip + cycle/jump shortcuts). Default true.

keymap?

Pick<TilingKeymap, "previousPane" | "nextPane" | "jumpToPane">

(Optional) Per-capability keybinding overrides for previousPane / nextPane / jumpToPane. These take precedence over the top-level keymap.

multiSelectGrouping?

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 group-leaves command (the same chord family as the Alt+G group key). Default true. A plain (no-modifier) header click keeps its normal focus/drag-pickup behavior; only altKey (Opt on macOS, Alt on Windows/Linux) presses participate in multi-selection. When ≥2 groupable panes are selected, a "Group" control appears in each selected pane's header; clicking it dispatches group-leaves (the clicked pane is the host slot) and clears the selection. The selection also clears on Escape, on a drag pickup, and on a plain click that establishes a single focus. When false, Alt/Opt+click and the Group control are no-ops. The grouping itself is independently gated by the grouping capability — with grouping disabled the Group control is suppressed (the multi-select dispatch is a safe no-op).

showContentToggle?

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 true. Set false to suppress the control for embeddings that always paint their own content via a custom renderTile (e.g. a docs/SEO surface), where the toggle is inert chrome. Suppressing the toggle hands content ownership to the embedding: pane-content is treated as VISIBLE by default (isPaneContentVisible initializes true) for ALL drag surfaces — in-tree panes, the source slot, the hop-in slot, and the portaled drag ghost — so the ghost body matches the seated body. With no control rendered, the flag cannot be flipped off, so this default holds for the lifetime of the embedding. (Ghost-seat reservation slots still render empty — that is a drag mechanic resolved by resolvePaneBodyRenderMode, independent of this flag.)

showSwitcherOverlay?

boolean

(Optional) Render the macOS Cmd+Tab-style visual switcher overlay while cycling panes with a held modifier (Alt+] / Alt+[). Default true. When true, a cycle press opens a centered overlay listing the panes and advances the highlight; the selection commits (focus / activate, and switch the maximized pane when maximized) on modifier release, and Escape cancels. When false, cycle presses activate the next/previous pane immediately (no overlay). The shortcuts work regardless of this flag.

showTabStrip?

boolean

(Optional) Render the TOP-LEVEL tab strip (the single strip across the top of the tiling region listing every pane). Default true. The cycle/jump shortcuts work regardless. This flag does NOT govern the per-group tab strip a tabbed-stacking group renders above its active member — that strip is governed by grouping.showGroupTabStrip (see TilingGroupingCapability).

tabDoubleClickMaximize?

boolean

(Optional) Toggle a pane's maximize/restore state when its TAB in the tab strip is double-clicked. Default true. The double-click dispatches the SAME toggle-maximize command (targeting the tab's leaf) the Alt+Enter keybinding dispatches, so the two converge on one maximize state — a double-click maximizes, a second double-click (or Alt+Enter, or Escape) restores. Single-click tab activation is unaffected. Also gated by the maximize capability: with maximize.enable: false the double-click is a no-op even when this is true. Set false to opt out (single-click tab activation only).

TilingPaneTitleBarControlsCapability

interface

Per-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 the growLeafToward reducer.

Signature:

interface TilingPaneTitleBarControlsCapability 

Properties

Property

Modifiers

Type

Description

acquireSpace?

boolean

(Optional) Render the per-pane directional (→ ← ↑ ↓) acquire-space controls. Default true.

sizing?

boolean

(Optional) Render the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control. Default true.

TilingRenderSurface

type

The 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

interface

Group 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

activateMember

readonly

(memberNumber: number) => void

Activate the member at memberNumber (1-based; a TilingGroupMemberView.memberNumber) — dispatches group-tab-jump.

groupId

readonly

string

The group node id.

members

readonly

ReadonlyArray<TilingGroupMemberView>

The group's members in member order (see TilingGroupMemberView).

removeMember

readonly

(leafId: string) => void

Eject the member with leafId from the group (it re-seats as a loose pane) — dispatches remove-from-group.

ungroup

readonly

() => void

Dissolve the whole group into loose panes — dispatches ungroup.

TilingResizeCapability

type

Resize 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 axis is "horizontal" (a flex-row container with a cursor-col-resize handle). - 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 whose axis is "vertical" (a flex-col container with a cursor-row-resize handle).

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

interface

Live-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

mode?

TilingSlotCommitmentMode

(Optional) Re-resolution policy after the ghost seats in a slot. Default "delta-responsive".

reresolveDeltaPx?

number

(Optional) delta-responsive re-aim threshold in CSS px. Default 24.

TilingSlotCommitmentMode

type

Live-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 beyond reresolveDeltaPx from 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

type

Orientation 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

interface

Split-divider / gap handle chrome across visible + hidden states.

Signature:

interface TilingThemeDividerTokens 

Properties

Property

Modifiers

Type

Description

base

readonly

string

Structural base incl. focus-visible ring color.

hidden

readonly

string

Hidden handle (no chrome, hit-area only).

visibleInteractive

readonly

string

Visible + resizable handle (resting + hover).

visibleStatic

readonly

string

Visible but resize-disabled handle.

TilingThemeGhostTokens

interface

Drag-ghost shell — the lifted, portaled copy of the dragged pane.

Signature:

interface TilingThemeGhostTokens 

Properties

Property

Modifiers

Type

Description

bodyText

readonly

string

Ghost body text color.

header

readonly

string

Ghost header bar.

subtitleText

readonly

string

Ghost subtitle text color.

surface

readonly

string

Ghost article shell (a touch more opaque + deeper shadow than a pane).

TilingThemePaneHeaderTokens

interface

Pane header chrome — resting + focused + the per-pane control buttons.

Signature:

interface TilingThemePaneHeaderTokens 

Properties

Property

Modifiers

Type

Description

base

readonly

string

Resting header bar: border-b, bg, inset sheen.

controlActive

readonly

string

Active/pressed header control button.

controlIdle

readonly

string

Resting header control button (maximize etc.).

focused

readonly

string

Additive classes when the pane is focused.

selected

readonly

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.

selectedBadge

readonly

string

The small neutral "selected" check affordance rendered in the header of a multi-selected pane. Neutral tone, distinct from any accent control.

titleText

readonly

string

Pane title base typography (accent color applied separately).

TilingThemePaneShellTokens

interface

Pane host shell surfaces + interaction-state rings.

Signature:

interface TilingThemePaneShellTokens 

Properties

Property

Modifiers

Type

Description

bodyText

readonly

string

Pane body scroll region text color/leading.

dragSourceOpacity

readonly

string

Opacity applied to the drag-source pane while it is picked up.

invalidDropRing

readonly

string

Ring on an invalid drop target.

subtitleText

readonly

string

Pane subtitle text color.

surface

readonly

string

Pane article shell: bg/gradient, radius, shadow/rim, backdrop-filter.

TilingThemeRootTokens

interface

Renderer-root + viewport surfaces.

Signature:

interface TilingThemeRootTokens 

Properties

Property

Modifiers

Type

Description

container

readonly

string

Outer renderer container: bg/gradient, radius, padding, outline.

viewport

readonly

string

Inner viewport (where the pane tree lays out): bg + radius.

TilingThemeTopBarTokens

interface

Top-bar / tab-strip chrome.

Signature:

interface TilingThemeTopBarTokens 

Properties

Property

Modifiers

Type

Description

container

readonly

string

Tab-strip container: border, bg, shadow, backdrop.

controlGroup

readonly

string

Switcher-control group wrapper (theme picker etc.).

pickerGroup

readonly

string

Accent-picker group wrapper.

switcherCard

readonly

string

Centered pane-switcher overlay card.

switcherCardInactive

readonly

string

Switcher card when the pane is NOT selected.

tabBase

readonly

string

Tab chip base typography/layout.

tabInactive

readonly

string

Inactive tab chip.

titleText

readonly

string

Strip title text.

TilingTileAccentSwatch

interface

A 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

accent

TilingTileAccent

The accent this swatch selects.

label

string

Human-readable label for the swatch (e.g. in a picker).

swatchClassName

string

Tailwind background class rendering the solid swatch dot.

TilingTitleBarSizingMode

type

The 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

interface

Touch-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 — when false, a touch press on the drag handle never starts a drag (touch is reserved for tap/scroll); mouse/pen drag is unaffected. Default true. - 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. Default 220.

Signature:

interface TilingTouchDragCapability 

Properties

Property

Modifiers

Type

Description

enable?

boolean

(Optional) Allow touch pointers to start a drag. Default true.

longPressMs?

number

(Optional) Long-press delay (ms) before a held touch becomes a drag. Default 220.

hypr-tiling follows calendar versioning; breaking changes and release notes live in the CHANGELOG.md. Source-available under PolyForm Perimeter 1.0.1 · free commercial use · no competing use.