Configuration and API
This guide explains behavior and constraints. The generated API reference lists the current public signatures for Core, Vanilla, and React.
Workspace API
Start with new Workspace({ container, paneTypes }) in vanilla or <Workspace paneTypes={...} /> in React. The library creates and owns the model. A container needs an explicit usable height. See the vanilla quickstart and React quickstart.
| Operation | Behavior |
|---|---|
addPane(type, options?) | Create from registered metadata; returns a pane handle, or undefined if its factory cancels |
getPane(id) / getPanes() | Retrieve handles for existing panes, including restored panes |
getLayout() / exportLayout() | Stable immutable snapshot / mutable JSON copy |
loadLayout(input) | Validate and replace arrangement, docking detached records |
exportWorkspace() / loadWorkspace(input) | Save/restore arrangement and appearance as parsed JSON objects |
reset() | Restore the initial workspace configuration |
on('change', listener) | Subscribe to layout, theme, tab-bar, and auto-collapse changes; returns unsubscribe |
on('error', listener) | Subscribe to renderer/model errors; returns unsubscribe |
closePane(id, options?) / closeGroup(id, options?) | Asynchronous confirmation and permission checks; explicit force: true bypasses both |
dispose() | Release views, windows, listeners, dialogs, and the internal model |
The remaining direct commands cover activatePane, movePane, updatePane, splitGroup, joinGroup, joinGroups, removeEmptyGroup, resize, resizeMany, maximize, setTabDisplay, setTabPlacement, canRestoreClosedPane, and restoreClosedPane. insertPane(record, groupId) is an advanced alternative for fully specified pane records. These explicit application commands retain core command validation and authority; only close commands add confirmation by default. Browser popouts always respect availability and permissions.
paneTypes maps type names to { title, render, ...metadata }. In React, render is a component. Optional create factories return partial pane metadata or undefined to cancel. addPane overrides factory metadata with explicitly supplied options, generates an ID if omitted, and uses the last active docked group or the first group. Explicit groupId and constraint failures never silently fall back to another group.
A pane handle has id, isDisposed, getSnapshot(), update(patch), setTitle(title), activate(), move(groupId, position?, index?), popout(placement?), return(), retry(), and close(options?). It survives view remounts and document transitions. Removal or a successful layout/workspace load invalidates it. Reacquire handles after loading or restoring closed panes. Stale synchronous operations throw; asynchronous operations reject.
Change events contain { action, changes }, where changes lists layout, theme, tabBar, and/or autoCollapse. A workspace load emits one consolidated event. Failed and cancelled changes emit none. Appearance-only events do not change the layout snapshot identity. Use exportWorkspace() inside a debounced save listener; subscriptions end on disposal.
Core layout JSON v1
interface Layout {
version: 1;
root: Group | Split;
panes: Record<string, Pane>;
popouts: Popout[];
maximized: string | null; // group id
}
interface Group {
kind: 'group';
id: string;
panes: string[];
active: string | null;
tabPlacement?: 'top' | 'left';
tabDisplay?: 'automatic' | 'compact';
}
interface Split {
kind: 'split';
id: string;
axis: 'horizontal' | 'vertical';
gap?: number; // pixels; defaults to 4
ratio: number; // preferred share of the first child, strictly between 0 and 1
children: [Group | Split, Group | Split];
}
interface Pane {
id: string;
type: string;
title: string;
icon?: string; // application icon key
params?: Json; // JSON only, not runtime state
header?: boolean;
confirmClose?: boolean; // optional UI confirmation; default inherits registration or false
capabilities?: Partial<
Record<
'resize' | 'move' | 'reorder' | 'addTab' | 'maximize' | 'split' | 'join' | 'close' | 'popout',
boolean
>
>;
size?: { minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number };
}
interface Popout {
paneId: string;
groupId: string;
index: number; // desired return location
placement?: { width?: number; height?: number; left?: number; top?: number };
}The version, root, panes, popouts, and maximized properties are required. Node IDs are unique across the tree; pane IDs are unique in their dictionary. Every pane appears exactly once, either in a group or the popout list. Empty groups have active: null. Maximum nesting depth is 64. JSON cannot contain cycles, functions, undefined values, nonfinite numbers, or class instances.
Sizes refer to the full pane region, including chrome. Defaults are minimum zero and no maximum. Tab groups satisfy the intersection of their panes' constraints, so incompatible tabs are rejected. Split children may leave unused space when a maximum prevents them filling the cross axis. A four-pixel divider (overridable with Split.gap) contributes to recursive minimum sizes. Below the combined minimum, the workspace scrolls. Above combined maximums, surplus space stays empty. Split ratios are preferences constrained by these limits, not guaranteed pixel proportions.
Capability flags default to true; omitted addTab and reorder inherit the resolved move flag for compatibility. Group-level operations require permission from affected panes: resizing a split checks both subtrees; tabbing and moving check the dragged pane and destination group; splitting checks the destination; joining checks the sibling region. Fixed bars normally disable all capabilities as well as specify size bounds. Host code can still deliberately reposition them.
Advanced browser-free core exports
createLayout(options?: { pane?: Pane; groupId?: string }): Layout creates validated, cloned JSON v1. With no pane it creates an empty group; otherwise it activates the supplied pane. The group defaults to main; supplied IDs are preserved. Invalid inputs throw LayoutError. Results have no popouts or maximized region and share no mutable data with inputs.
Both adapter entry points re-export LayoutError, createLayout, parseLayout, validate, and core types. The model type is named LayoutSnapshot in adapters to distinguish it from React's Workspace component. React also exports TabRegistry, themes, themeFamilies, and public DOM options/view/handle types. See migration notes for removed pre-release APIs.
parseLayout(unknown): Layout clones and validates or throws LayoutError. validate(unknown): Issue[] returns {path, message} issues. bounds, allocate, groups, paneIds, findNode, and findParent are pure helpers.
new LayoutStore(input, options?) creates a single state owner. The optional LayoutStoreOptions.autoCollapse accepts AutoCollapse ('enabled' | 'protected' | 'disabled'), defaulting to 'disabled'. getAutoCollapse() reads the session setting and setAutoCollapse(mode) validates and updates it without altering the current layout. Core load/reset preserve this setting; raw layout JSON excludes it. Workspace presets serialize it, and loadWorkspace applies the saved setting. Enabled collapses regions emptied by close, move, or popout; protected exempts popouts; disabled preserves all empty source regions. Use removeEmptyGroup(groupId) or the empty tab bar’s X to explicitly remove an empty region (except the final root).
Commands clone, validate, and commit atomically. A failed command leaves the previous snapshot intact and reports to onError subscribers before throwing.
| Method | Behavior |
|---|---|
getSnapshot() | Stable, deeply frozen snapshot; treat it as read-only |
export() | Mutable clone suitable for JSON serialization |
subscribe(listener) | Observe {action, layout}; returns unsubscribe |
onError(listener) | Observe command/subscriber failures; returns unsubscribe |
load(input) | Replace with validated JSON atomically |
reset() | Restore constructor configuration |
activate(groupId, paneId) | Select an existing tab |
add(pane, groupId, options?) | Insert a new pane as a tab |
updatePane(pane) | Replace metadata/constraints with validation |
split(groupId, axis, newPane, options?) | Create a new region after the existing group |
joinRegions(receiverId, otherId, options?) | Join the inclusive contiguous row/column range into the receiver, crossing same-axis split ancestry; preserve all content as tabs |
join(groupId, options?) | Collapse its parent split, retaining all sibling content as tabs |
move(paneId, groupId, position?, index?, options?) | Position is tab, left, right, top, or bottom; index is a tab insertion index after detachment |
resize(splitId, ratio, options?) | Set a preferred split proportion |
resizeMany(ratios, options?) | Atomically update a map of split IDs to preferred proportions |
maximize(groupId | null, options?) | Maximize or restore a region |
close(paneId, options?) | Remove pane and placement |
popout(paneId, placement?, options?) | Pure model transition; does not open a browser |
returnPane(paneId) | Return to a compatible original/fallback group, or a new region |
setTabPlacement(groupId, placement?) | Set top/left orientation; omit to inherit |
setTabDisplay(groupId, display?) | Set automatic/compact display; omit to inherit |
removeEmptyGroup(groupId, options?) | Remove an empty region except the final root |
closeGroup(groupId, options?) | Atomically close docked tabs in a region |
canRestoreClosedTab() | Report whether closed-tab history is available |
restoreClosedTab() | Restore the most recently closed tab with compatible placement |
dispose() | End subscriptions; idempotent; subsequent commands fail |
Options accept {source: 'user' | 'api'}; default is api. Flags only restrict user commands. Use the mounted renderer's popout method, not the store's pure popout command, to open browser windows. A DOM renderer interprets detached records without live companion handles as restored data and docks them without retaining reopening intent.
DOM exports
new Workspace({ container, ...options }) appends its own scoped root; it does not clear unrelated host content. Options:
initialLayoutorinitialWorkspace: optional, mutually exclusive initial configuration. Omit both for an empty workspace. A preset supplies its saved theme, tab bar, and auto-collapse setting, taking precedence over initial appearance options. Quilt owns the store; externalstoreinput is rejected.renderers: pane-type-to-renderer registry.getPaneState(id): optional application-owned reference for each view mount.tabs:TabRegistryof available content. Factories return pane metadata, or undefined to cancel. Omitted instance IDs are generated.registry: combines creation metadata and renderers; mutually exclusive withtabsandrenderers.messages: typed text overrides for chrome and dialogs.shortcuts: opt-in presets, explicit key/modifier bindings, or external registrations with menu hints.commands: optional application command registrations (id,label,execute,enabled).shortcutConflictPolicy:priority(default),warn, orerror.onShortcutConflict(conflicts): optional diagnostics callback in warn mode.formatShortcut(binding): optional display formatter; matching and ARIA encoding are unchanged.popouts: workspace-level availability, default true.confirmClose(request): optional application dialog replacing the built-in close confirmation.renderIcon(key, document): optional icon resolver.themeandtabBar: theme tokens and workspace/group tab-bar configuration; see Theming.onError(error): mount, interaction, and window failures.prepareWindow(window, pane): copy additional application styles/assets into a companion document.openWindow(pane, placement): optional synchronous, same-origin window factory; null means blocked. The library owns this returned window and replaces its body, so do not return an existing unrelated application window.
The workspace additionally provides:
canExecuteAction(action, context?): check a built-in action or custom command without prompting.executeAction(action, context?): Promise<boolean>: run the same action as a menu click, including capability checks and confirmation. Context acceptsgroupIdandpaneId.getShortcuts(): readonly RegisteredShortcut[]: immutable normalized registrations.getShortcutConflicts(): immutable collisions with normalized bindings and command IDs.registerShortcut(id, registration): replace one binding; returns an ownership-safe cleanup.registerCommand(command): add a custom command; returns a cleanup for it and its bindings.getCommands(): immutable custom command metadata for application help and palettes.
WorkspaceAction, CommandId, CustomCommandId, ActionContext, CommandContext, CommandRegistration, RegisteredCommand, ShortcutRegistration, RegisteredShortcut, ShortcutConflict, ShortcutConflictPolicy, and ShortcutConflictError are exported by both packages. The exported formatShortcut(binding) helper supplies the default display formatting. See registered actions for action IDs, targeting, external handlers, and change notifications.
| Method | Purpose |
|---|---|
updateOptions(partial) | Update options in place; omitted keys remain unchanged, explicit undefined resets them |
setTheme(theme) | Replace theme overrides; {} clears them |
setTabBar(options) | Replace tab-bar settings; {} restores defaults |
refreshTheme() | Recompute CSS geometry and synchronize companions |
exportWorkspace() | Export layout and appearance with a docked layout copy |
loadWorkspace(input) | Validate and apply a workspace preset |
popout(id, placement?): Promise<boolean> | Open a companion from a user action; resolve when mounting succeeds or fails |
returnPane(id) | Dock a live companion pane |
retryPane(id) | Retry a failed view |
closePane(id) / closeGroup(id) | Request pane or group closure, including confirmation when configured |
dispose() | Release views, listeners, dialogs, companions, and the owned store |
Call workspace.dispose() once to release its resources. There is no public store property.
React exports
Workspace accepts the same options except container (it creates its own host), replacing renderers with components: Record<string, ComponentType<PaneProps>>, plus className and style. Its ref exposes the mounted handle. Use onReady(handle) for initialization. Before readiness, synchronous methods throw Workspace is not ready; asynchronous methods reject with the same error. React owns disposal on unmount. Initial configuration changes do not reset the session.
reactRenderer(Component) adapts a React component for mixed vanilla/React consumers. useLayoutSnapshot(workspace) subscribes with React's external-store API. React 18.3 and 19 are peer-compatible; packed-consumer tests compile and exercise both React versions.
PaneProps<State> includes document, window, pane, state, reportError, signal, onCleanup, and location; the vanilla PaneContext<State> also receives element. State defaults to unknown. See typed state and option resets.
Theme variables
Override .layouts variables in your application stylesheet: --layouts-bg, --layouts-panel, --layouts-header, --layouts-text, --layouts-muted, --layouts-line, --layouts-accent, --layouts-focus, and --layouts-radius. Styling remains scoped; application content is yours. No OS-dependent motion overrides are installed.
Tab icons and shortcuts
Pane.icon?: string is an optional, serializable application key. Supply renderIcon(key, document) to the vanilla mount options or React <Workspace>. Return a fresh decorative DOM element, or undefined for an unknown key. The library falls back to the first character of the title, keeps the full title as the accessible name and tooltip, and never interprets icon keys as markup. The demos use a small Lucide registry; consumers can use any icon library without adding Lucide to their production dependencies. See demos/icons.ts.
Tabs shrink according to their own available width. Below 130px per tab, only the active tab keeps its close button; below 95px, labels hide and icons remain. If even icon tabs cannot fit, the strip scrolls. Drag to either half of another tab to insert before or after it, including within the same pane region. Closing respects capabilities.close; locked tabs have no close button.
Convenience shortcuts are disabled by default:
new Workspace({
container: host,
initialLayout,
renderers,
shortcuts: true, // enable all conveniences
});
// Or select individually:
// shortcuts: { maximize: true, middleClickClose: false, addTab: true }Backtick (`) or Option+Space (Alt+Space) toggles workspace maximize/restore for the hovered pane region, falling back to the keyboard-focused region. This fills the layout host; it does not invoke the browser Fullscreen API. Text inputs, editable content, dialogs, and repeated key events are left alone. Some operating systems reserve Alt+Space and may intercept it before the page receives it; the actions menu remains available. Middle-click closes a tab when enabled and permitted. T opens the new-tab picker in the hovered region when registered tabs and its addTab capability allows adding a tab (falling back to move when omitted). Handled events call preventDefault(). The typing, dialog, and repeat safeguards also apply. Use T without modifiers. Normal tab arrow-key navigation and divider keyboard resizing remain available regardless of this convenience setting.
Register available tabs
A TabRegistry describes what users can create; the layout snapshot describes what is already open. Registering a type does not mount it. Removing a registration prevents new instances without closing existing panes.
import { TabRegistry, Workspace } from 'quilt-vanilla';
const tabs = new TabRegistry();
const unregister = tabs.register({
id: 'canvas',
title: 'Canvas',
description: 'Interactive scene viewport',
icon: 'canvas',
keywords: ['scene', 'viewport', '3d'],
create: ({ source, groupId }) => ({
id: crypto.randomUUID(),
type: 'canvas', // corresponding renderer/component registry key
title: 'Scene',
icon: 'canvas',
}),
});
const workspace = new Workspace({ container: host, initialLayout, renderers, tabs });
// unregister(); // also updates a currently open pickernew TabRegistry(entries) accepts an initial array. register rejects duplicate or empty IDs and returns an idempotent unregister function. list() returns registrations in insertion order; subscribe(listener) returns cleanup. Registration IDs identify choices; pane IDs identify individual open instances. Factories supply type, metadata, constraints, and initial params. IDs are optional; Quilt generates omitted IDs once per creation and preserves explicit IDs. Return undefined to cancel. They run only after an explicit selection. Core validation remains atomic, so a rejected creation preserves the workspace.
Add tab and split actions open a searchable combobox tray. Search matches titles, descriptions, and keywords. Arrow keys move selection, Enter creates, and Escape cancels modal choosers. With autoCollapse disabled, clicking outside a new split’s region dismisses its chooser while preserving the empty region. Click its empty surface to reopen search, or close the region explicitly. Add tab activates the new tab in the same region. Split creates a new region containing the selected type. Canvas is an ordinary pane renderer and can be added, tabbed, dragged, closed, maximized, and popped out under the same rules.
Pass tabs to React <Workspace> too. Keep the registry object stable and register or unregister entries as features mount or unmount. Content creation uses registered choices; the legacy createPane callback has been removed. Empty regions can still split into empty regions without a registry. Existing movement/split capability checks still apply. See Theming for the separate theme API.
Menu actions include built-in decorative icons. The optional renderIcon resolver can override them using layouts:add-tab, layouts:split-right, layouts:split-below, layouts:join, layouts:maximize, layouts:restore, layouts:popout, layouts:close, and layouts:cancel. Return undefined to use the built-in icon. Icons inherit text color, including disabled and themed states.
Interactive divider resizing preserves distant pane boundaries. Only branches touching the divider absorb the size change, stopping at their minimum or maximum sizes. The DOM renderer computes the coupled ratios and commits them through resizeMany; React shares this behavior. resize remains a low-level proportional command. Resizing the whole workspace still uses the stored proportions.
Empty regions display diagonal guide lines. Click the empty content area, or focus it and press Enter/Space, to open tab search. Registry factories receive source: undefined when the workspace has no remaining source pane.
joinRegions accepts JoinOptions: capability handling matches other commands. Optional extents maps every node ID in the affected row/column (including splits) to its rendered size along the join axis. The DOM renderer supplies these measurements to preserve actual sizes under constraints and custom divider widths. Without measurements, the command preserves proportional shares. joinRange(root, from, to) returns the eligible range for previews, or undefined when endpoints are identical, absent, or separated by a perpendicular split. Content constraints and capabilities are validated atomically when committing. The existing join command and “Join sibling region” menu retain their parent-collapse behavior.
Workspace presets and interaction options
WorkspacePreset v1 stores layout, theme, tabBar, and autoCollapse. The exported layout is docked without changing live windows. parseWorkspace validates an independent preset; dockLayout normalizes a layout copy. Raw core exports retain live detached records.
registry: PaneRegistry<PaneRenderer> replaces separate renderers and tabs when supplied. React accepts PaneRegistry<ComponentType<PaneProps>>. Registrations combine type, title, view, optional create, and optional confirmClose.
Options add messages, popouts (default true), and an optional async confirmClose(request) replacement for the built-in dialog. Confirmation is only requested for panes opting in through metadata or their registration. KeyBinding accepts key and optional ctrl, alt, shift, meta; configurable shortcut actions accept one binding, an array, or their existing boolean preset.
See Web integration for complete usage and lifetime rules.
Interaction policy and reusable shortcuts
Both demos open a Settings tab in the left pane. Use it to try workspace defaults, region and tab overrides, auto-collapse, popouts, shortcut presets, middle-click closing, per-tab close confirmation, size limits, and tab orientation/display. Changes apply live. Interaction choices last for the demo session; Reset interaction rules clears capability overrides and restores workspace behavior defaults.
Import defaultShortcuts from quilt-vanilla or quilt-react to opt into the same shortcuts as the demos. It is a frozen, spreadable object: backtick and Alt+Space maximize/restore, T adds a tab, R restores a closed tab, and middle-click closes a tab. Importing it installs no listeners. shortcuts: true remains equivalent.
import { Workspace, defaultShortcuts } from 'quilt-vanilla';
const workspace = new Workspace({
container: host,
initialLayout,
renderers,
shortcuts: { ...defaultShortcuts, addTab: false },
capabilities: {
defaults: { resize: true, close: false, move: false },
groups: { editor: { move: true } },
panes: { notes: { close: true } },
},
});Quilt calls a content tab a Pane, and the region containing tabs a Group. capabilities accepts defaults, groups keyed by region ID, and panes keyed by tab ID. Each scope accepts these flags:
| Flag | User interaction |
|---|---|
resize | Resize dividers affecting the region/tab |
addTab | Create a new tab through the content picker |
reorder | Reorder tabs within their current region |
move | Move a tab between regions or into a new edge split |
maximize | Maximize a region |
close | Close a tab or its region |
split | Split a region, including edge drops |
join | Join regions |
popout | Open a tab in a companion window |
For backward compatibility, omitted addTab and reorder flags fall back to the resolved move permission. Explicit values at any scope separate these operations. For example, defaults: { move: false, reorder: true, addTab: true } allows arranging tabs and creating content while keeping tabs in their regions. Reordering requires permission from all tabs in the target region; moving checks the dragged tab and destination tabs. Edge drops also require split permission. To disable all tab dragging, disable both move and reorder.
Maximization checks every tab in the region. Restoring an already maximized region remains allowed even if its policy changes, so users can always return to the full layout. Direct maximize(id, { source: 'user' }) also enforces this permission.
popout uses the same workspace, region, and tab precedence. The existing popouts: false workspace option remains a hard off switch: more specific capability overrides cannot re-enable it. Policy changes cancel pending openings, with the original view retained. Existing companions stay open and can always return; return/closing the companion is not blocked by popout: false.
For each flag, precedence is session panes override, saved Pane.capabilities, session groups override, workspace defaults, then true. An explicit true can override a less specific false. To prohibit an operation on every tab, remove more specific overrides too. Closing a region requires all its tabs to allow closing; resizing a divider requires both subtrees to allow resizing. Empty regions use their group override and workspace defaults.
Use workspace.can(paneId, capability) for one tab or workspace.canNode(groupOrSplitId, capability) for an entire region/subtree. Menu actions, keyboard actions, close buttons, middle-click, drag/drop, resize handles, and corner gestures share this policy. Close permission is rechecked after asynchronous confirmation. Direct layout commands preserve their existing host authority; pass { source: 'user' } to enforce permissions where supported. These controls are interaction rules, not a security boundary.
Replace policy live using workspace.updateOptions({ capabilities: nextPolicy }), or change the React capabilities prop. Omitting a scope in the replacement clears its previous overrides; passing undefined resets session policy. Changes emit a workspace change event containing capabilities. Content views are retained. Policy is session configuration and is not saved in layout or workspace JSON; existing per-tab Pane.capabilities remains serializable.
Related controls already available include split, join, popout, per-tab close confirmation, size bounds, and automatic empty-region collapse. These are independent of the interaction flags above.
Resize presentation
Set resizeMode: 'gutter' | 'border' on a Vanilla workspace or React component. Use updateOptions({ resizeMode: 'border' }) to change it live. The Theming pane’s Resizing section in both demos includes a Resize style selector. The demos start in border mode; handle width and disabled-handle controls appear only in gutter mode.
gutter(default) reserves space between panes. The entire gutter is the resize target; explicit split gaps and theme resize widths keep their existing behavior.borderdraws one shared 1px line between panes, with no reserved gutter. A grip appears on hover, keyboard focus, or dragging. The invisible target is centered on the line and usesresizeHandleWidth, with an 8px minimum. Explicit split gaps and disabled-handle widths do not reserve space in this mode. Disabled dividers retain a decorative line but have no resize target.
Both modes preserve pointer and keyboard resizing, minimum/maximum dimensions, and capability restrictions. Touch-capable devices get an invisible target at least 24px wide across the divider, without changing pane geometry. On touch-only devices, the border grip stays visible. Dragging follows the initiating finger; other touches cannot move or end that drag. Cancellation or lost pointer capture ends resizing at its current position. The shared line uses the theme's line color; disabled lines use frozenPaneBorder, and the grip uses accent. Changing modes preserves content views and layout ratios. It emits a workspace change containing resizeMode. This option is session configuration and is not included in exported layout or workspace JSON; clearing it restores gutter mode.
Scrollbar options
WorkspaceSettings.scrollbars?: ScrollbarOptions is shared by Vanilla and React. ScrollbarOptions exposes visibility?: 'always' | 'auto-hide', placement?: 'overlay' | 'gutter', revealOn?: 'pointer' | 'scroll' (default 'scroll'), and hideDelay?: number (milliseconds). New workspaces and empty options objects default to auto-hide, overlay, scroll intent only, and 500 ms. Explicit scrollbars: undefined selects native controls. updateOptions({ scrollbars: undefined }) restores native controls. Workspace change events include 'scrollbars' when the setting changes. Workspace presets serialize the optional configuration; older presets restore native controls. See scrollbar behavior and nested registration.
PaneContext.registerScrollArea(elements: ScrollAreaElements): () => void registers an application-owned nested host and direct-child viewport, returning idempotent cleanup. ScrollAreaElements contains host: HTMLElement and viewport: HTMLElement. React PaneProps includes the same callback. Registrations inherit live workspace options and are removed automatically on pane disposal.
Pane context menu
Set the global contextMenu option to 'button' (integrated), 'right-click', 'both' (default), or 'none' in Vanilla or React. Change it live with updateOptions({ contextMenu: 'right-click' }); explicit undefined restores the default. The Theming pane’s Tab section in the demos includes the same four choices.
The integrated button mode shows the menu button in every pane header, including empty regions. Pinned regions hide the header and integrated button. Right-click opens the same menu at the pointer on a tab bar or corner split/join handle, using that region's active tab. Pane content and modal backdrops do not open Quilt menus; content right-click behavior belongs to the application. Application context menus that prevent the event's default behavior take priority, as do embedded workspaces. Disabling right-click leaves the browser's native context menu available. This is session configuration, like resizeMode, and is not saved in layout JSON or workspace presets.
Set contextMenuIcon: 'three-dots' | 'one-dot' to choose the integrated button's global appearance. Three dots is the default; explicit undefined restores it. The setting applies live to populated and empty pane headers in Vanilla and React, without changing the button's hit area or accessible label. The demo exposes it as Integrated button appearance under Theming → Tab. Like contextMenu, this option is session configuration and is not saved in workspace presets.
Dedicated panes
Groups can persist dedicatedPaneId?: string in Layout JSON v1. A dedicated group contains exactly that pane, or is empty while the designated pane has a popout record pointing to the group. Invalid combinations are rejected atomically. Older layouts omit the field and retain ordinary tab behavior.
workspace.dedicateGroup(groupId): Promise<boolean> keeps the active pane and closes inactive docked tabs after checking their permissions and close confirmations. It resolves false on cancellation, stale confirmation, or failure. workspace.releaseGroup(groupId): void restores ordinary tab behavior without reopening closed tabs. Both methods are available through the shared Vanilla and React workspace handle. Built-in actions are dedicatePane and releasePane; neither has a default keyboard binding.
Standalone core callers use store.dedicateGroup(groupId, options) and store.releaseGroup(groupId). Core commands are synchronous and do not show confirmation UI; { source: 'user' } enforces inactive-tab close permissions. Structural dedication restrictions apply to both API and user commands. See dedicated region lifecycle for restoration, popout, and application-data behavior.