Rich, web-friendly editor UI framework for SolidJS + TypeScript
solid-stage docs editor-side-navigation-patterns.md
20 kB · 128 lines
Markdown
at main

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). 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, Unity — Customize your workspace). 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). 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, 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). VS Code's own guidance leans on this — "use a clear, obvious name for the View Container" (Activity Bar guidelines) — and the product mitigates by letting the Activity Bar move to the top/bottom and toggle a compact size (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). The escape hatch for "I need a panel from another tab visible" is pinning a panel so it shows across tabs (Blender — Tabs & Panels) — 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). 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 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).
  • 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). 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). 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 Sections 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):

Blender (primary — manual + HIG):

Other editors (primary docs):

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 (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.