Layout switch
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), 1pxvar(--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,useContextPanelSupportinapps/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
titleattribute for the tooltip — use the DSTooltip.
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.
Related
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