Primary Secondary Neutral Surface

## Responsive Shell Sidebar pattern

Resize this viewport: at ≤900px the leading + trailing sidebars collapse to drawers triggered by the toolbar buttons. Above 900px the sidebars are inline. The same content is mounted in both surfaces; consumer signals drive both copies via the parent template's re-read.

## The pattern

Three pieces:

1. Inline mounts for the sidebars wrapped in `.wide-only` (display: contents at >900px; display: none at ≤900px).
2. Drawer twins with the same children, mounted outside the shell's named slots, controlled by toolbar buttons inside `.narrow-only`.
3. MediaQueryList listener that auto-closes the drawers on viewport widening (the gap FEEDBACK-06 §3 explicitly flagged).

## Dual-mount caveat

Components with local DOM state (uncontrolled form inputs, scroll position, focused element) will lose state when the viewport crosses the breakpoint — the inline copy unmounts and the drawer copy mounts (or vice versa). Signal-bound components are safe because their state lives in the signal, not the DOM. Most AdiaUI primitives are signal-backed; the affected cases are:

- Native `<input>`/`<textarea>` with no signal binding
- Components using `this.querySelector(...)` with imperative state
- Scroll-position-sensitive containers (e.g. chat threads at a specific scroll)

For these, either: (a) lift the state to a signal in the parent, or (b) document the unmount-on-resize edge.

## Auto-close on viewport widening

const mql = window.matchMedia('(max-width: 900px)'); mql.addEventListener('change', (e) => { if (!e.matches) { // Viewport widened past breakpoint — close any open drawers document.getElementById('leading-drawer').open = false; document.getElementById('trailing-drawer').open = false; } });

## State sharing

Single signal source consumed by both copies. The parent template reads the signal once and passes the extracted value to both the inline mount AND the drawer mount; on signal change the parent re-renders both:

import { signal } from '@adia-ai/web-components/core'; const activeToken = signal('primary'); static template = () => { const current = activeToken.value; // dep read here return html` <aside class="wide-only"> <list-ui> ... .active=${current === 'primary'} ... </list-ui> </aside> <drawer-ui id="leading-drawer"> <list-ui> ... .active=${current === 'primary'} ... </list-ui> </drawer-ui> `; }

## Future v0.6.0: built-in `responsive-breakpoint`

FEEDBACK-06 §3 Option A proposed a built-in `<editor-sidebar responsive-breakpoint="900">` attribute that auto-stamps the drawer twin. Deferred to v0.6.0 — requires editor-shell + pane-ui semantics audit (focus handoff at the inline↔drawer transition, dual-mount event-listener deduplication, etc.). Pairs with a `<show-when breakpoint="...">` trait. Until then, the pattern above is the canonical recipe.

Lightness Chroma Hue
Primary Secondary Neutral Surface
Lightness Chroma Hue