On this page
@inklu/dock
Updated October 2026
@inklu/dock is a headless workspace dock: a tabbed, multi-panel surface that sits docked beside your app, reserving width, or lifts into an expanded modal over it with a backdrop, focus trap and scroll lock. You register panel types once, open them through a typed handle, and style stable data-dock-* attributes with plain CSS.
Installation
pnpm add @inklu/dock
Requires React 19. Import the stylesheet once, alongside your own CSS:
Quick start
Call createDock once, at module scope, with every panel type the app can open. definePanel only infers the payload type; it returns its argument unchanged.
open returns the panel id, or null when the type is unknown or the dock is full and nothing can be evicted.
AI setup
Paste this prompt into Claude Code, Cursor or any coding agent with write access to your project. It needs no other context.
For an agent that keeps working in the repo, install the agent skill instead. It carries the rules, not just the setup:
pnpm dlx skills add nkurunziza-saddy/inklu --skill inklu-dock
Agents reading the site start at /dock/llms.txt. The complete reference, including what this page leaves out, is /dock/llms-full.txt.
Panels
component receives isActive, so a backgrounded panel can pause polling or media without unmounting; its DOM stays alive, keeping scroll and local state.
Opening a payload that matches an open panel selects that tab instead of adding a second one. It matches on key(payload) if declared, then on the serialized URL params, then on a payload.id field.
Provider
Every prop is read live. Defaults: limit 5, widths 400 (320–720) docked and 768 (up to 1280) expanded. At the limit, the least recently used clean, unguarded panel is evicted.
Forms and guards
useDockForm gives a panel draft state, dirty tracking and a close guard in one hook. The guard only exists while the form is dirty, and a failed onSubmit keeps the draft.
open moves focus into the panel it opened. Mark the field that should receive it with data-dock-autofocus: unlike autoFocus, the mark is used every time open lands on the panel, not only when it mounts. Without a mark, the panel itself is focused.
Any other panel can veto closing with useDockLifecycle. A guarded panel is never evicted at the tab limit.
URL state
The selected panel is mirrored into the URL, so a link or a reload reopens it, and back/forward move between panels. The url shorthand takes string-valued payload keys only; for anything else, write serialize and deserialize:
Pass session to the provider to reopen every tab on reload, or url={null} to turn URL state off.
Styling
Every rule in styles.css is wrapped in :where(), so one class overrides it. Theme it with CSS variables:
Target parts by attribute: data-dock-surface, data-dock-tab, data-dock-panel and so on. data-dock-state moves through entering, present and exiting, so a CSS transition on it is a custom animation.
API
Identity rules, composing Surface and TabStrip yourself, persistence adapters and the lower-level store are in the README, also served for agents as /dock/llms-full.txt.