Skip to main content

Pinned tabsEnterprise

Pinned tabs let you mark the tabs that matter so they stay put. Modelled on VS Code and Chrome, a pinned tab:

  • renders first: pinned tabs always sort ahead of unpinned tabs in their group;
  • stays reachable: pinned tabs are excluded from the ordinary overflow dropdown, so they stay visible as the strip fills up. If the pinned block is itself too wide to fit, the clipped pinned tabs surface in their own "Pinned" section at the top of the dropdown rather than becoming unreachable;
  • resists reordering: you cannot drag an unpinned tab to the left of a pinned one (the pin boundary is enforced).

The pinned order is stable: pinning appends a tab to the end of the pinned block, and unpinning returns it to the front of the unpinned block.

Enabling pinned tabs

Pinned tabs are dormant until you enable them through the pinnedTabs option. With the option unset (or enabled: false) pinning is a no-op and the tab strip behaves exactly as it does by default.

const api = createDockview(element, {
pinnedTabs: { enabled: true },
});

Options

compact
Render pinned tabs icon-only (title + close button hidden), VS-Code / Chrome style. Default false; dockview's default tab has no favicon, so pinned tabs stay labelled (with a pin glyph) unless you opt in. Best enabled alongside a custom tab renderer that shows an icon.
compact?: boolean
contextMenuItem
Add a Pin/Unpin item to the tab context menu (requires ContextMenuModule). Default true.
contextMenuItem?: boolean
enabled
Master switch. Default: undefined (dormant; pinning is a no-op).
enabled?: boolean
mode
'inline' (default) keeps pinned tabs first within the existing strip; 'separate-row' renders them on their own VS-Code-style row. (Phase 1 implements 'inline' only.)
mode?: 'separate-row' | 'inline'
stickyScroll
In 'inline' mode, keep pinned tabs frozen to the left edge when the tab strip scrolls horizontally (Chrome-style sticky columns); each pinned tab sits at the cumulative width of the pinned tabs before it. Default true; set false to let pinned tabs scroll away with the rest. Ignored in 'separate-row' mode, where the pinned row is always visible.
stickyScroll?: boolean
togglePinOnCrossBoundaryDrag
Drag a tab across the pin boundary to toggle its pinned state (VS-Code-style). Default false; dragging across the boundary is clamped back, matching Chrome, where pinning is an explicit action only.
togglePinOnCrossBoundaryDrag?: boolean
  • enabled: the master switch. Leave it unset (or false) to keep pinning dormant; the tab strip then behaves identically to the default.
  • mode: 'inline' (the default) keeps pinned tabs first within the existing tab strip. 'separate-row' renders them on their own VS-Code-style row above the main strip; the row collapses when no tab in the group is pinned.
  • compact: render pinned tabs icon-only (title and close button hidden), like VS Code and Chrome. Defaults to false because Dockview's default tab has no favicon, so pinned tabs stay labelled (with a pin glyph) unless you opt in. Best paired with a custom tab renderer that shows an icon.
  • togglePinOnCrossBoundaryDrag: when true, dragging a tab across the pin boundary toggles its pinned state (VS-Code behaviour). Defaults to false: dragging across the boundary is clamped back, matching Chrome, where pinning is an explicit action.
  • stickyScroll: in 'inline' mode, keep pinned tabs frozen to the left edge when the strip scrolls horizontally (Chrome-style sticky columns), so they stay visible even once the strip is scrolled. Defaults to true; set false to let pinned tabs scroll away with the rest. Ignored in 'separate-row' mode, where the pinned row is always visible.
  • contextMenuItem: add a built-in "Pin"/"Unpin" item to the tab context menu. Defaults to true; requires the context menu to be available.

mode: 'separate-row' renders pinned tabs in a dedicated row. In 'inline' mode pinned and unpinned tabs share the single strip, with pinned tabs sorted to the front.

Pinning by dragging

In 'separate-row' mode the pinned row is a live drop target, so pinning is a direct gesture as well as an API call:

  • Pin: drag an unpinned tab from the main strip onto the pinned row. It is pinned and placed at the slot where you drop it within the pinned block.
  • Unpin: drag a pinned tab out of the row and back into the main strip. The row is the only handle on a pinned tab in this mode, so dragging it into the unpinned area is always treated as a deliberate unpin.
  • Reorder: drag a pinned tab within the row to reorder the pinned block; a drop indicator marks where it will land.

In 'inline' mode the pin boundary is enforced during drags: an unpinned tab cannot cross to the left of a pinned one. Set togglePinOnCrossBoundaryDrag to let a tab dragged across that boundary toggle its pinned state instead.

Keyboard

With the keyboard keymap enabled, Ctrl+Shift+Enter pins or unpins the active panel's tab. The binding is rebindable through keyboardNavigation.keymap.togglePin and is inert unless pinnedTabs.enabled is true.

Panel API

Pin, unpin and observe a tab programmatically through its panel api.

isPinned
Whether this panel's tab is pinned. Pinned tabs render before unpinned tabs, never overflow, and resist cross-boundary reorder. Owned by the PinnedTabs module. Reads false until a panel is pinned, which requires pinnedTabs.enabled (both setPinned and restore are gated on it), so a component with pinning disabled always reports false.
readonly isPinned: boolean
onDidChangePinned
readonly onDidChangePinned: Event<PinnedChangeEvent>
setPinned
Pin or unpin this panel's tab. No-op (warns once) when the PinnedTabs module is not registered, and dormant unless pinnedTabs.enabled is set.
setPinned(pinned: boolean): void
const api: DockviewPanelApi;

// pin the tab (renders first, excluded from overflow)
api.setPinned(true);

// unpin the tab
api.setPinned(false);

// read the current pinned state
const result: boolean = api.isPinned;

// react to pinned state changes
api.onDidChangePinned((event) => {
console.log(event.isPinned);
});

You can observe pin changes across the whole component through the container api:

onDidPanelPinnedChange
Fired when a panel is pinned or unpinned (PinnedTabs module). Carries the panel and its new isPinned state.
onDidPanelPinnedChange: Event<DockviewPanelPinnedChangeEvent>
const api: DockviewApi;

api.onDidPanelPinnedChange((event) => {
console.log(event.panel.id, event.isPinned);
});

setPinned only takes effect when pinnedTabs.enabled is true. With pinning disabled, isPinned always reads false and setPinned is a no-op.

Context menu

When contextMenuItem is left enabled (the default) a "Pin"/"Unpin" item is added to the tab context menu automatically, toggling based on the tab's current state. If you want to place it yourself, set pinnedTabs.contextMenuItem: false to stop the automatic injection (otherwise the item appears twice), then include the built-in 'pin' item in your own builder:

getTabContextMenuItems={({ panel }) => [
'pin',
'separator',
'close',
'closeOthers',
'closeAll',
]}

Serialization

Pinned state round-trips through toJSON / fromJSON. The pinned flag is written only for pinned panels, so existing layouts stay byte-stable, and the pinned-first order is preserved by the serialized panel order.

const state = api.toJSON();
// ... later
api.fromJSON(state); // pinned tabs are restored, in their pinned order

Layouts saved before pinning was enabled (or restored into a component with pinnedTabs disabled) load as a normal, unpinned strip. The pinned key is simply ignored when pinning is off.

See also

  • Tabs: custom tab renderers and the tab context menu that hosts the 'pin' item.
  • Tab overflow: the overflow dropdown that pinned tabs are excluded from.
  • Advanced overflow: the dropdown's "Pinned" section when the pinned block itself overflows.
  • Multi-row tabs: an alternative way to keep every tab visible as the strip fills up.
  • Keyboard navigation: the togglePin binding for pinning from the keyboard.