Skip to content

Layout: regions and docks

This is a guide, not the contract. What the platform guarantees is specified under openspec/specs/. For this page: shell-layout. Where this page and a specification disagree, the specification is right, and that is a defect in this page: change the behaviour there, then explain it here.

provideLayout declares which regions sit where in the border topology. A region has a type (its anatomy) and a dock (where it sits):

  • Docks: top · bottom · left · right · center.
  • Region types:
    • bar: a thin strip of items in start | center | end slots (top bar, status bar). A bar that cannot show every item folds the ones that do not fit into a More control at its end. The last item of the end slot folds first, highest order first, then center, then start. The control opens the folded items in a tray, as they are. On a narrow viewport, the one at which the sidebars become overlays, the workbench’s own top-bar items take a compact form: the product’s mark without its name, the language switcher as its symbol alone.

    • rail: the rail, which the workbench labels Activity bar, holding icon triggers for commands.

    • panel: a sidebar surface that hosts views (the host auto-tabs multiple views). A panel is the one region a person resizes, so it is the one that may declare its widths in pixels, each optional. Its width is what it shows until someone resizes it and what resetting the layout returns to. Its minWidth and maxWidth bound dragging, the keyboard and a width set from code. Undeclared values stay at 256, 180 and 480. A width a person released is kept even if you later change width, and one stored above a new maxWidth is shown at the new maximum.

      On a narrow viewport the panel becomes an overlay, and its overlayWidth sizes it there (default 288). The workbench caps that at the screen’s width less a margin, so the overlay never runs off the screen and there is room to dismiss it. The overlay width is not dragged or stored, and the widths beside the content do not reach it.

      { id: 'right-panel', type: 'panel', dock: 'right', width: 360, minWidth: 280, maxWidth: 640, overlayWidth: 400 }

      The type only allows these fields on a panel region (PanelRegion; a bar, rail or content region is a NonPanelRegion). A panel declaring any of the four widths as something other than a positive number, whose minWidth exceeds its maxWidth, or whose width lies outside its own bounds, makes provideLayout throw, naming the region.

    • content: the main content area (docks center). URL-addressed (routes), not views.

A weaver targets a region by its id, never by its dock or type: registerSurface({ docks: ['left-panel'] }), registerRailItem({ rail: 'primary' }) and registerBarItem({ bar: 'status-bar' }) name the ids the scaffold declares. Left and right are symmetric; shell anatomy lists the ids.

Non-routable surfaces render only in panel regions. A surface’s home dock (docks[0]) may name any region id, but one docked into a content (or bar/rail) region is a silent no-op (dev-mode warns). The content area is routed: a weaver fills it with a surface that declares routable: { path } (see The content area).

Collapsing, resizing and hiding views in the sidebars from your own code is SidebarService in the the Distribution API.

Panes and splits

Every dock (centre + both sidebars) is a tree of tab-group panes. Users split a pane by dragging a tab to its edge or via a tab’s Split right / Split down menu, move tabs between groups by dragging onto a strip, and resize with the dividers. Exactly one centre pane is the address pane (it drives deep links / back-forward); the rest are workspace state. The whole arrangement (pane trees, sizes, active tabs) is persisted user-locally and reload-safe. Each of these gestures has its switch in Switching capabilities off.

Curating a sidebar

The user curates a sidebar the way they curate the rail. A right-click on a view tab offers Move to other sidebar and Hide; a right-click on the strip offers Customize views, which opens a dialog listing every view with where it sits: hidden, left, or right. Picking a place moves it there, so the dialog does the hiding and the moving in one control, and a view hidden on the left comes back wherever you send it. The dialog has a search field and scrolls, because a product with many views would otherwise be a wall of rows. Which views a sidebar holds is part of the workspace, so switching workspaces changes it; the rail’s own curation stays put.

The dialog is the command shell.views.customize, so it is reachable from the command palette, bindable to a shortcut, callable from an item of your own, and removable with provideShell({ omit: ['shell.views.customize'] }). The menu entry is a contribution of its own (menu:shell.views.customize), so you can drop the entry and keep the command.

Where next

  • Sidebars: collapse, resize, hide and show by region id from your own code.
  • Panes: the panes your tabs sit in, read and driven from your own code.
  • Surfaces and panes: why a pane is a tab group and a surface can sit anywhere.