Drawer slot

The place in the app shell where a screen can float a panel next to its content — a record preview, a create form, the search preview. CSS apps/web/src/ds/app-shell.css (.as-drawer, .as-drawer-left); the slot is always rendered by AppShell inside the page plane, and a screen puts content into it through <ShellDrawer open side onClose> (apps/web/src/components/shell/ShellDrawer.tsx).

Two decisions are already made for the screen, and both matter:

  • The slot is never unmounted. It is hidden when empty, so a panel's content — a half-written form, a scroll position — survives closing and reopening.
  • The side is chosen per opening, away from where the user clicked (useDrawerSide): the panel never covers the row that opened it.

When to use

  • Reading or editing something that belongs to the list behind it, while the list stays usable: a record preview, a create form for the list, a search result preview.
  • Any panel that must not block the page.

When not to use

  • A task that must be finished before the screen continues — that is a Dialog.
  • Content that needs the whole screen — that is a route.
  • A menu anchored to a control — that is a Popover.
  • A modeless window the user drags around and minimises — that is Dialog with window states.

Anatomy

  • Slot .as-drawer: position: absolute inside the page plane, width: clamp(404px, 33.333vw, 640px), z-index: 50. The slot itself does not take clicks (pointer-events: none); its children do — so an empty slot never blocks the page under it.
  • Side .as-drawer-left mirrors the default right dock.
  • Entry animation is gated by prefers-reduced-motion.
  • Content is whatever the screen portals in; in practice the Record drawer chrome (.pdr) or a Drawer form.
  • There is no scrim and no role="dialog" on the slot — non-modal by design.
  • There is no full-plane variant. .as-drawer.full was a mirror of the upstream canon with no consumer since records started opening in a new tab; it was removed on 11 September 2026 (NAV-1).

Best practices

Do

  • Let the side be decided by useDrawerSide; do not hard-code right.
  • Keep the panel's own chrome (head, close, footer) inside the content component — the slot only positions.
  • Close on the panel's own control; the page behind must never need a click to "get back".
  • Keep the listing behind it live: selection, scrolling and keyboard must keep working.

Don't

  • Add a scrim or block the page — that turns a drawer into a bad dialog.
  • Set a width on the content; the slot owns the width so every drawer in the app matches.
  • Stack two drawers; one slot, one panel.
  • Bring back a full-plane drawer; a record that needs the whole plane is a route.

Content

  • The panel inside says what it is (the record's name, Nový kontakt); the slot itself carries no text.
  • record-drawer — the usual content of this slot.
  • drawer-form — the create/edit variant.
  • dialog — when the task must block.
  • Stories: components--drawer-slot--default, --variants, --states, --composition.

Source: docs/app-shell/drawer-slot.usage.md · GitLab