Components › Navigation › App shell › Layout switch

Layout switch

canon

Floating group at the bottom of the side menu that switches the layout: collapse the menu, list layout, context panel.

Owner 29 Sep 2026 (FA214, dev-be): "create it, add to DS and apply across the app, it should work the same everywhere". Replaces the "Sbalit navigaci" row in every module. Up to 3 segments (menu [ · list layout · context panel ]), canonical IconButton with pressed, 1px divider. AD 30 Sep: the floating group sits over a scrolling menu, so --shadow-sm is allowed (overlay rule, RULES §4); phone ≤720 hidden. Becomes beta when the shared shell component and its story are on main. On main 30 Sep (acrm !4216, !4221, !4259); docs from dev-be FA226.

Used in 1 file · 1 area · 1× — components/AppShell

The floating pill anchored to the bottom of the left nav, used to switch between the layouts a screen offers — the sidebar itself, and (when the current screen supports it) its list and its context panel. CSS apps/web/src/components/shell/layout-switch.css (.pp-layout, .pp-ldiv, .pp-float); React LayoutSwitch in apps/web/src/components/shell/LayoutSwitch.tsx (app-side, DS candidate). It replaces the earlier "Collapse navigation" footer row in every workspace (ACRM-384/385).

The pill is a shell singleton, not a component a screen mounts: AppShell renders it once, next to the sidebar, in every workspace. A screen never draws its own layout controls; it opts into the two optional segments by registering support for them.

When to use

  • The sidebar segment (menu full ↔ icons-only) is always present — nothing to opt into.
  • The list-layout segment: a list screen that can also show a preview next to a selected row (wide vs. split), e.g. the Komunikace list.
  • The context-panel segment: a detail screen with a collapsible side panel, e.g. a message detail.

When not to use

  • A screen-specific view switch (table vs. board, this month vs. all time) — that belongs in the screen's own toolbar, not in the shell.
  • A third, unrelated toggle. The pill's width and the rail's vertical stack are measured for three segments; a fourth needs a design decision first.

Anatomy

  • Pill .pp-layout: border-radius: var(--radius-md), 1px var(--border), var(--surface-100), box-shadow: var(--shadow-sm). role="group" with one accessible name for the whole cluster.
  • Up to three segments, always in this order, separated by a 1px divider .pp-ldiv:
  • Menu (always present) — full labels vs. icons-only rail.
  • List layout (opt-in) — wide vs. split.
  • Context panel (opt-in) — show vs. hide.
  • Collapsed menu (rail, 56px, ≥901px only): the pill stacks vertically and centers under the icon column (.as-side.rail .pp-layout); the divider rotates to horizontal.
  • Phone (≤720px): the pill is hidden entirely — the sidebar itself does not render, and no screen registers a segment below that width.
  • A screen opts a segment in or out through two small external stores, not React context (useListLayoutSupport, useContextPanelSupport in apps/web/src/lib/layout-slots.ts); the pill reads whichever screen registered last and draws only the segments that are registered.

Best practices

Do
  • Register a segment only while your screen genuinely offers that layout; unregister (or pass enabled: false) the moment it stops applying, e.g. on a route that never shows a preview.
  • Keep the segment order fixed (menu, list, panel) — it matches the fixed order in the pill.
  • Reuse the keyboard shortcuts ([ for menu, ] for panel) instead of adding a screen-local one.
Don’t
  • Don't draw a screen's own copy of these buttons — register the slot instead.
  • Don't give the menu segment aria-pressed; it is an action that switches the whole sidebar, not a two-state toggle of itself (see a11y doc).
  • Don't use the native title attribute for the tooltip — use the DS Tooltip.

Content

  • Tooltip copy states the action and, where a shortcut exists, the key in parentheses: "Collapse menu to icons ( [ )". The same string is the button's accessible name.
  • sidebar-nav-item — the menu segment folds this component's rail.
  • drawer-slot — a typical consumer of the context-panel segment.
  • Stories: components--layout-switch--default, --variants, --states.

Source: docs/app-shell/layout-switch.usage.md · GitLab