# Side-panel / activity-bar navigation for property-heavy editors **Scope:** Deciding the side-navigation model for a reusable `solid-stage` primitive whose consumer apps put a 3D canvas in the center and heavy property editing in the side panels. The fork: **Model A** (collapse stub — the icon rail just re-expands the same panel) vs **Model B** (activity-bar view router — each icon swaps in a different task's panel) vs a hybrid. ## TL;DR and recommendation - **Build Model B, with Model A folded in as a behavior — not a separate mode.** Almost no modern editor ships *pure* Model A. The icon rail that only re-expands one fixed pane is the legacy Visual Studio behavior; every property-heavy editor we surveyed (Blender, VS Code, Figma, Godot, Adobe) uses the rail/tab strip to **route between different task views**, and the best of them (VS Code) *also* lets clicking the active icon collapse the bar — so Model A is just "the active view is currently collapsed," a state of Model B, not a competing design. - **The user's hypothesis is correct:** Model B scales to multi-task workflows; Model A only re-links the pane you already had open, which is dead weight the moment an app grows past one property surface (Properties vs Outliner vs Materials vs Export — exactly the OSRS/exporter case). - **Two independent collapse levels, not one.** Mature editors separate (a) *which view is active* on the rail from (b) *which sections inside that view are expanded*. Blender does this explicitly: vertical tabs switch the whole property group, panels inside each tab collapse independently, and a "pin" escape-hatch shows a panel across tabs ([Blender manual — Tabs & Panels](https://docs.blender.org/manual/en/latest/interface/window_system/tabs_panels.html)). `solid-stage` already has the inner level (`Section` collapse); the new primitive owns the outer level. - **Single-view-at-a-time is the right default.** Model B shows one view at a time by design ("the content of only one Tab is visible at a time" — Blender). Apps that genuinely need two property surfaces side by side want *docking* (Unity/Unreal/Adobe), which is a different, much heavier paradigm we should not chase in a v1 library primitive. - **The one real cost of Model B is icon discoverability** — unlabeled icon rails are a documented, repeatedly-filed VS Code complaint. Mitigate in the primitive by supporting **icon + label** rails (or labeled tooltips), not icon-only. - **Concrete shape:** the app supplies an ordered list of views, each `{ id, icon, label, sections }`; the primitive renders the rail + the active view's `Panel`, keeps one view active, lets the active view collapse to a rail stub, and persists `{ activeView, collapsed }` through the existing `createPersistedToggle` / `persist.ts` infra. Details in the last section. ## How real editors actually structure side navigation Pure Model A is rare; the dominant pattern is a **view router** at one of two altitudes — per-panel (an icon/tab strip that swaps the side panel's contents) or whole-window (named task layouts). Classification of what clicking a side element actually *does*: | Editor | Side-nav mechanism | Clicking an item does… | Closest model | | --- | --- | --- | --- | | **VS Code** | Activity Bar → Side Bar view containers | Switches the Primary Side Bar to a different view container (Explorer / Search / SCM / Run / Extensions); clicking the **active** icon collapses the Side Bar | **B**, with A folded in | | **Blender — Properties editor** | Vertical color-coded icon column | Switches the entire property group shown (Render, Output, Scene, Object, Modifiers, Material…); panels inside collapse independently | **B** | | **Blender — N-panel sidebar** | Vertical category tabs (per add-on/workflow) | Switches which add-on/category's panels show; whole region toggles with `N` | **B** + region collapse | | **Blender — Workspaces** | Horizontal top tabs | Swaps the whole editor layout for a task (Layout, Modeling, Sculpting, Shading…) | **B** (whole-window) | | **Figma** | Right-sidebar tabs: Design / Prototype / Inspect | Swaps the entire right panel to a different task's controls; also context-sensitive to selection | **B** | | **Godot** | Main-screen buttons (2D / 3D / Script / AssetLib) + fixed side docks | Top buttons route the center workspace; Inspector dock is an always-present context panel, not a router | **B** (center) + fixed docks | | **Adobe (Photoshop / After Effects)** | Workspace bar (named presets) + freely dockable panels | Workspace presets swap which panels are shown for a task; panels themselves freely dock/float | **B** (presets) over docking | | **Unreal Engine** | Dockable tabbed panels + editor **Modes** | Details panel is a fixed context panel (follows selection); Modes (Landscape, Foliage, Modeling…) swap the mode-specific toolbar | Docking + partial B via Modes | | **Unity** | Freely dockable tabbed windows | Drag tabs to dock/stack/float anywhere; Inspector is an always-present context panel | Docking (neither A nor B) | Three takeaways from the table: 1. **Routing wins.** Six of nine surfaces use the side strip to switch *tasks*, not to re-expand one pane. The OSRS editor's current Model A is the outlier, not the norm. 2. **"Context panel" ≠ "router."** Unity's Inspector and Unreal's Details panel are always-present panels whose *contents* follow the selection ([Unreal Editor Interface](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-editor-interface), [Unity — Customize your workspace](https://docs.unity3d.com/Manual/CustomizingYourWorkspace.html)). That is orthogonal to the A/B question — those editors answer "switch between tasks" with **docking**, not a rail. 3. **Two altitudes of routing.** Per-panel routing (VS Code activity bar, Blender Properties tabs, Figma tabs) is the one that maps to a *side-panel primitive*. Whole-window routing (Adobe/Blender workspaces, Godot main-screen) is a layout-manager concern, above what a single side-panel component should own. VS Code is the reference implementation worth copying: the Activity Bar is "a core navigation surface" where each item maps to a View Container, and "clicking an Activity Bar item switches focus to its associated View Container in the Side Bar" ([VS Code — Activity Bar UX guidelines](https://code.visualstudio.com/api/ux-guidelines/activity-bar)). A view container holds one or more views; with a single view the toolbar collapses its actions inline, with multiple it adds a `…` overflow — and 3–5 views is "a comfortable max" ([VS Code — Sidebars](https://code.visualstudio.com/api/ux-guidelines/sidebars), [Views](https://code.visualstudio.com/api/ux-guidelines/views)). ## What users prefer, and where each model hurts **The literature does not frame this as "A vs B"** — it treats routing as the default and argues about *labeling* and *switching cost*. That itself is the answer: Model A is not a contender people advocate for; it's the thing modern editors evolved away from. **Model B's cost — icon discoverability.** Unlabeled icon rails are a repeatedly-filed VS Code complaint: "I keep forgetting what all of the sidebar icons mean. It would be so much easier if they were labeled with words that I can read and instantly know what they do without having to waste time hovering" ([vscode#130051](https://github.com/microsoft/vscode/issues/130051)). VS Code's own guidance leans on this — "use a clear, obvious name for the View Container" ([Activity Bar guidelines](https://code.visualstudio.com/api/ux-guidelines/activity-bar)) — and the product mitigates by letting the Activity Bar move to the top/bottom and toggle a compact size ([Custom Layout](https://code.visualstudio.com/docs/configure/custom-layout)). Takeaway for us: **an icon-only rail is the failure mode; support labels.** **Model B's other cost — tab-switching fatigue.** Blender's own Human Interface Guidelines design directly against it: "Make as few tabs as possible… Make tabs workflow oriented, enabling tab usage without excessive tab switching," and create a new tab only when "the tools being added clearly belong to a specific workflow… that does not yet exist" ([Blender HIG — Sidebar Tabs](https://developer.blender.org/docs/features/interface/human_interface_guidelines/sidebar_tabs/)). The escape hatch for "I need a panel from another tab visible" is **pinning** a panel so it shows across tabs ([Blender — Tabs & Panels](https://docs.blender.org/manual/en/latest/interface/window_system/tabs_panels.html)) — i.e. Blender solves cross-view needs without abandoning single-view-at-a-time. **Model A's cost — "where did my panel go?"** The collapse-stub model has its own confusion, which is why VS Code keeps the icon strip *visible* when the Side Bar is collapsed and is being pushed to keep that consistent across activity-bar positions: users want the icons to "serve dual duty as both view selectors and sidebar collapse toggles" ([vscode#260437](https://github.com/microsoft/vscode/issues/260437)). The lesson: if you collapse, **leave the rail visible as the affordance to bring it back** — a bare edge with no icons is the disorienting case. Net: the usability record favors **a labeled router that can also collapse, leaving its rail visible** — which is neither pure A nor pure B. ## Model B trade-offs, and why a hybrid is the sweet spot for a library The brief's Model-B questions, answered against what shipping editors actually do: - **Icon-only vs icon+label.** Icon+label (or guaranteed tooltips) wins; icon-only is the documented discoverability failure. Blender hedges by **color-coding** the Properties icons by purpose ([Blender Basics / katsbits](https://www.katsbits.com/codex/blender-basics/) corroborates the manual). A library should let the app choose icon-only, icon+label, or label-only. - **Indicating the active view.** Every router highlights the active item (VS Code active Activity Bar item; Blender active tab). Non-negotiable; the rail needs a clear active state distinct from hover/focus. - **Can a Model-B bar still collapse entirely?** Yes, and it should. VS Code's Primary Side Bar fully hides (`workbench.sideBar` toggle, ⌘B) while the Activity Bar persists; Blender's N-panel toggles with `N`. Collapse and routing are independent ([Custom Layout](https://code.visualstudio.com/docs/configure/custom-layout)). - **Coexisting with per-panel/section collapse.** This is the two-level model: the rail switches *views*; sections *inside* a view collapse independently (Blender panels collapse with a disclosure arrow regardless of which tab is active). `solid-stage`'s `Section` already owns the inner level — the new primitive must not fight it. - **Single view vs multiple simultaneously.** Model B is single-view-at-a-time ("only one Tab visible at a time"). Genuine multi-panel-at-once is a *docking* feature (Unity/Unreal/Adobe), a categorically heavier system. For a property-editing library, single-view + an optional pin-style escape hatch (later) is the right scope; don't build docking. - **Persisting the active view.** Editors persist it: "VS Code will remember the layout of views and panels across your sessions" ([Custom Layout](https://code.visualstudio.com/docs/configure/custom-layout)). We already have `createPersistedToggle` / `persist.ts`; persist `activeView` and `collapsed` the same way. **Why hybrid for a *library* specifically:** a library primitive serves apps at different scales — the exporter might have two views, a future tool ten. VS Code is the existence proof that one widget covers the whole range: it routes (B), collapses the active view to a rail stub (A), hides entirely, and pairs with a Secondary Side Bar for a second simultaneous surface ([Sidebars](https://code.visualstudio.com/api/ux-guidelines/sidebars)). Hard-committing to A would cap the consumer apps; hard-committing to "always-expanded B" would annoy the small ones. **Make routing the model and collapse a state**, and a one-view app degrades gracefully to "looks like a plain panel with a single rail icon." ## Recommendation: the primitive shape Build a **Model-B view router with Model-A collapse as a state**, configuration-driven, sitting on top of the existing `Panel` / `Section` / `TabMenu` primitives rather than replacing them. Working name aside, the shape: **The app supplies a list of views** (this is the whole configuration surface): ``` views: Array<{ id: string; // stable key, used for persistence icon: JSX.Element; // lucide icon, matches existing icon usage label: string; // REQUIRED — drives tooltip and optional inline label sections: JSX.Element; // the Section set rendered when this view is active badge?: ... // optional count/indicator, like VS Code's SCM badge }> ``` **Behavior defaults (all overridable via props):** - **One active view at a time.** `activeView` is a single id. Clicking a different icon swaps `sections`. This is the Blender/VS Code default and the right one for property editing. - **Active-icon click collapses.** Clicking the icon of the already-active view collapses the panel body to the bare rail (the Model-A behavior, as a state). Clicking any icon while collapsed re-expands into that view. This is exactly VS Code's activity-bar toggle. - **Rail stays visible when collapsed.** Never collapse to a blank edge — keep the icon strip as the re-expand affordance (the vscode#260437 lesson). A separate, explicit "hide the whole rail" is a second, coarser toggle if an app wants it. - **Rail presentation is a prop:** `railVariant: 'icon' | 'icon-label' | 'label'`, default `icon-label` (or guaranteed tooltips on `icon`). Do **not** default to icon-only — that's the documented discoverability trap. - **Active state is mandatory styling**, distinct from `:hover` and the existing `--stage-color-focus` keyboard ring. - **Inner section collapse is untouched.** Each view's `sections` are ordinary `Section`s that collapse independently; the router never collapses them for you. - **Persistence:** persist `{ activeView, collapsed }` per instance key via `createPersistedToggle` / the `persist.ts` path, matching how panel/section state is already stored. On load, restore the active view; fall back to `views[0]` if the stored id no longer exists (views can change between releases — handle the stale-id case explicitly). **Explicitly out of scope for v1** (anti-recommendations, so we don't gold-plate): - **No docking / multi-view-simultaneous.** That's the Unity/Unreal paradigm; it's a layout manager, not a side-panel primitive. If an app truly needs two surfaces at once, that's a second `Panel` instance (mirroring VS Code's Secondary Side Bar), not a feature of this widget. - **No cross-view "pinning" yet.** Blender's pin is a nice escape hatch but adds real state complexity; defer until an app asks. Note it as the planned answer to "I need one panel visible across views." - **No whole-window workspace switching.** Adobe/Blender workspaces operate above a single panel; that belongs to `EditorPanels`-level layout, not here. This lands the primitive squarely on the user's hypothesis — Model B as the structural bet — while absorbing Model A as the collapsed state so the OSRS editor's current behavior is still expressible (a single-view router that's collapsed by default looks and acts like today's stub). It reuses `Panel`, `Section`, `TabMenu`, and the persistence layer, so it's additive, not a rewrite. ## Sources **VS Code (primary — UX guidelines + product docs):** - [Activity Bar — UX guidelines](https://code.visualstudio.com/api/ux-guidelines/activity-bar) - [Sidebars — UX guidelines](https://code.visualstudio.com/api/ux-guidelines/sidebars) - [Views — UX guidelines](https://code.visualstudio.com/api/ux-guidelines/views) - [User interface — getting started](https://code.visualstudio.com/docs/getstarted/userinterface) - [Custom Layout — configure](https://code.visualstudio.com/docs/configure/custom-layout) - [vscode#130051 — label activity bar icons](https://github.com/microsoft/vscode/issues/130051) - [vscode#260437 — keep activity bar icons visible/clickable to toggle sidebar](https://github.com/microsoft/vscode/issues/260437) **Blender (primary — manual + HIG):** - [Tabs & Panels — Blender manual](https://docs.blender.org/manual/en/latest/interface/window_system/tabs_panels.html) - [Sidebar Tabs — Blender Human Interface Guidelines](https://developer.blender.org/docs/features/interface/human_interface_guidelines/sidebar_tabs/) - [Blender Basics — katsbits (Properties icon color-coding, corroborating)](https://www.katsbits.com/codex/blender-basics/) **Other editors (primary docs):** - [Unreal Editor Interface — Unreal Engine docs](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-editor-interface) - [Level Editor Details Panel — Unreal Engine docs](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-editor-details-panel-in-unreal-engine) - [Customize your workspace layout — Unity Manual](https://docs.unity3d.com/Manual/CustomizingYourWorkspace.html) - [Inspector Dock — Godot docs](https://docs.godotengine.org/en/stable/tutorials/editor/inspector_dock.html) - [Design, prototype, and inspect in the right sidebar — Figma Learn](https://help.figma.com/hc/en-us/articles/360039832014-Design-prototype-and-inspect-right-sidebar-) - [Workspaces, panels, and viewers — Adobe After Effects](https://helpx.adobe.com/after-effects/using/workspaces-panels-viewers.html) - [Switch workspaces — Adobe Photoshop](https://helpx.adobe.com/photoshop/desktop/get-started/learn-the-basics/switch-workspaces.html) ## Further research - **Web-based 3D/motion editors not yet examined.** The brief named Spline and Theatre.js as "other web-based 3D/motion editors"; I classified seven desktop/web editors thoroughly but did not fetch Spline or Theatre.js docs. Worth a quick follow-up to confirm they don't introduce a side-nav pattern the desktop tools lack — initial expectation is they mirror Figma's tabbed-right-panel (Model B), but that's **[unverified]**. - **Pinning / cross-view panel ergonomics.** Blender's pin is the only first-class "show one panel across views" mechanism I found in primary docs. If we later add it, worth studying how Blender scopes pin state (per-panel, persisted?) before designing ours — the manual documents the behavior but not the persistence/scoping rules. - **Active-icon-collapses-sidebar is documented via an issue, not the manual.** The exact "click the active Activity Bar icon to collapse the Side Bar" behavior is confirmed by [vscode#260437](https://github.com/microsoft/vscode/issues/260437) (describing it as existing default for left/right positions) rather than a dedicated docs page; the getting-started UI doc does not spell it out. Treated as established behavior, but the canonical primary statement is the issue, not the manual. - **Quantitative usability data is thin.** The discoverability concern is well-attested qualitatively (repeated VS Code issues, Blender HIG cautions) but I found no controlled study measuring icon-only vs icon+label task time for property editors specifically. If this decision needs harder backing, that's the gap. - **Docs accessibility note.** `docs.blender.org` and `developer.blender.org` returned HTTP 403 to `WebFetch`; content above was retrieved via the webscraping skill's tier-2 `curl_cffi` (Safari/iOS impersonation), which returned the full pages cleanly. All other sources fetched directly.