Design System

Overlays & disclosure

Modal and non-modal surfaces for focused tasks and contextual detail, plus progressive-disclosure patterns for hiding secondary content until needed. All overlays are built on Radix primitives for focus trapping, dismissal and typeahead behaviour.

Overlay Surface

Floating contextual surfaces use a subtle glass treatment to create depth while maintaining readability and accessibility. Modal surfaces remain predominantly solid to preserve focus and clarity.

/* tokens: --overlay-surface, --overlay-surface-subtle, --overlay-border, --overlay-shadow, --overlay-backdrop, --overlay-blur */

When to use

  • Soft Glass — Popover, DropdownMenu, ContextMenu, CommandMenu, HoverCard and Toast: translucent surface, 16px backdrop blur, hairline border, soft elevation.
  • Subtle Glass — Drawer: near-solid surface with a reduced blur, because it is a large reading and editing surface.
  • Solid — Dialog and AlertDialog: the existing surface, border and elevation tokens, unchanged.

When not to use

  • Cards, tables, navigation and page chrome — glass belongs to floating surfaces only.
  • Anything holding a destructive confirmation or a focused task.
  • As decoration: the treatment supports hierarchy, it is never the visual focus.

Playground

Open each tier and compare it against the page behind: Soft Glass, then Subtle Glass, then Solid.

Surface tiers

Soft Glass

Popover · Dropdown · Command · Hover card · Toast

--overlay-surface

Subtle Glass

Drawer

--overlay-surface-subtle

Solid

Dialog · AlertDialog

--color-surface

Accessibility

  • Surfaces are theme-neutral translucency over the current surface colour — no brand hue is mixed in, so a rebrand leaves the glass untouched.
  • Without backdrop-filter support the same surfaces paint near-opaque instead, so text, icons and controls stay readable.
  • Contextual overlays float without a full-screen backdrop; only Dialog, AlertDialog and Drawer dim the page behind them.
  • Blur is static and never animated; open/close motion uses the shared motion tokens and collapses under prefers-reduced-motion.
  • State is still carried by the existing semantic surface tokens (hover, selected, disabled, destructive) — never by transparency.

Dialog

Modal window that interrupts the current task and requires an explicit action or dismissal. Use for focused, self-contained tasks like editing a record.

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/design-system";

When to use

A focused edit or confirmation task that should block interaction with the page behind it. Avoid for content the user needs to reference alongside the page — use a Drawer or Popover instead.

Editing a single record, a short multi-field form, or a one-off confirmation that isn't destructive (use AlertDialog for destructive confirmations).

Sizes

sm, md (default) and lg control the max width.

Accessibility

  • Focus moves into the dialog on open and is trapped there; Escape and the overlay click restore focus to the trigger.
  • DialogTitle and DialogDescription wire aria-labelledby/aria-describedby automatically — always include a DialogTitle, even if visually hidden.
  • The close IconButton always carries an accessible label and can be hidden only when another explicit action closes the dialog.

AlertDialog

A modal reserved for confirming actions that are destructive or otherwise hard to undo. Unlike Dialog, it cannot be dismissed by clicking outside — the user must choose Cancel or Action.

import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogAction, AlertDialogCancel } from "@/design-system";

When not to use

Don't use AlertDialog for routine confirmations or forms — reserve the forced choice for genuinely destructive actions to avoid alert fatigue.

Deleting an account, permanently removing a file, or discarding unsaved work with no undo.

Destructive confirmation

Tones

neutral, warning and destructive. Each renders a matching leading icon so the severity is never carried by colour alone.

Accessibility

  • Radix sets role="alertdialog" and forces focus onto Cancel by default, so the safer option is chosen if the user presses Enter without reading.
  • Outside clicks and Escape do not close it silently — the user must actively choose an option.
  • Use only for irreversible or high-stakes actions; a plain Dialog is enough for reversible ones.

Drawer

A modal panel that slides in from an edge of the screen. Suited to longer forms or detail views where a centred Dialog would feel cramped, or to mobile-style bottom sheets.

import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose } from "@/design-system";

When to use

Prefer Dialog for short, centred tasks; reach for Drawer when the content is a longer form, a filter panel, or benefits from an edge-anchored, larger surface.

A detail panel for a table row, an advanced filter form, or a mobile bottom sheet.

Sides

right (default), left, top and bottom.

Accessibility

  • Shares Dialog's focus trap, Escape handling and focus restoration — it's the same Radix Dialog primitive with a different visual treatment.
  • The `side` prop determines the slide direction; keep DrawerTitle present for every drawer.

Popover

Non-modal panel anchored to a trigger, for small amounts of contextual content or a compact form. The page behind it stays interactive.

import { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverTitle, PopoverDescription, PopoverFooter, PopoverClose } from "@/design-system";

Compact form

Header and footer

PopoverHeader / PopoverTitle / PopoverDescription / PopoverFooter give filter and settings panels a consistent structure.

Accessibility

  • Not modal: focus is not trapped, and clicking outside or Escape closes it while returning focus to the trigger.
  • Use for supplementary content only — anything required to complete the primary task belongs in a Dialog or Drawer instead.
  • Radix collision detection keeps the panel inside the viewport, flipping side and shifting alignment as needed.

ContextMenu

Menu triggered by a right-click (or long-press) on a specific element, offering actions scoped to that element.

import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut } from "@/design-system";

Right-click the card below

Right-click this area

Accessibility

  • Also fully keyboard operable via the Menu/Shift+F10 key, so it never traps mouse-only users.
  • Always pair with an equivalent visible action (e.g. a DropdownMenu or button) since right-click discoverability is low.
  • Shares DropdownMenu's item styling, including the same destructive treatment — one visual language for both menus.

HoverCard

Reveals a small preview of content on hover or focus of a trigger, such as a user's profile summary behind their name.

import { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardTitle, HoverCardDescription } from "@/design-system";

Author preview

HoverCardTitle and HoverCardDescription keep preview content typographically consistent; the avatar slot is optional.

Accessibility

  • Opens on both pointer hover and keyboard focus, so it doesn't rely on a mouse.
  • Content must be supplementary, never required — it can be dismissed at any time and isn't reliably reachable on touch devices.
  • Anything essential in the card must also exist on the destination page, since touch users may never open it.

CommandMenu

Searchable command palette (built on cmdk inside the Dialog composite) for quickly finding and running actions, jumping to pages or searching records, typically bound to ⌘K.

import { CommandMenuDialog, CommandMenuInput, CommandMenuList, CommandMenuEmpty, CommandMenuLoading, CommandMenuGroup, CommandMenuItem, CommandMenuSeparator, CommandMenuShortcut, useCommandMenuShortcut } from "@/design-system";

When to use

A global, keyboard-first way to jump to actions or content across the whole app. Not a replacement for a persistent primary navigation.

App-wide "quick switcher" / command palette, typically triggered from a header button or a keyboard shortcut.

Interactive

Recent items, grouped suggestions, a disabled item and an empty state. useCommandMenuShortcut binds ⌘K / Ctrl+K — try it now.

Loading

CommandMenuLoading renders the Spinner while results are being fetched, above any already-known items.

Accessibility

  • Inherits Dialog's focus trap, Escape-to-close and focus restoration to the trigger that opened it.
  • The input is focused automatically on open; arrow keys move through filtered items and Enter selects the active one.
  • An empty state communicates zero matches to screen reader and sighted users alike instead of an empty list.
  • Disabled items stay announced but unselectable, so a temporarily unavailable action doesn't silently disappear.

Accordion

A set of expandable sections where headings summarise the content below. Supports single (one open at a time) or multiple open sections.

import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/design-system";

When to use

Grouped FAQ-style content or settings sections where most items stay collapsed. Avoid nesting more than one level, and avoid it for content users need to compare side by side.

An FAQ list, or a settings page split into named sections.

Single, collapsible

You're billed monthly based on active seats, prorated for mid-cycle changes.

Accessibility

  • Each trigger is a real button with aria-expanded, wrapped in an h3-equivalent header via AccordionPrimitive.Header.
  • Content animates height via CSS custom properties and respects prefers-reduced-motion, which the global stylesheet disables automatically.

Collapsible

The low-level show/hide primitive Accordion and Disclosure are built on. Use it directly when you need a bare expand/collapse region with fully custom trigger markup.

import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/design-system";

Custom trigger

Advanced options

Rate limits, retries and webhooks

Accessibility

  • CollapsibleTrigger is a real button with aria-expanded and aria-controls managed by Radix.
  • Prefer Accordion for a set of related sections, and Disclosure for a single summary/detail block — reach for Collapsible only when neither fits.

Disclosure

A self-contained summary line with a chevron that reveals supporting detail — a simpler alternative to Accordion for a single expandable block.

import { Disclosure } from "@/design-system";

When to use

One optional block of extra detail (e.g. an error's stack trace, or terms and conditions). Use Accordion instead when you have several related sections.

Showing a full error message, optional terms and conditions, or “advanced” fields in a form.

Examples