[Bug Fix] Dialog: match shadcn Base UI + vega

Merged
CI
5d ago
+574-42

Related issue

Related to #570. Stacked on #580 and targets its branch; retarget to main once #580 merges.

Description

Brings Dialog in line with shadcn Base UI + vega, fixing what the shadcn-audit run on #580 flagged. A new audit run on this branch reports no failures on Dialog.

Look

  • Panel: bg-popover text-popover-foreground ring-1 ring-foreground/10 rounded-xl gap-6 text-sm, no shadow. In dark mode the text used to be black (the UA CanvasText); it now uses the popover foreground.
  • Overlay: ::backdrop is bg-black/10 with backdrop-blur-xs (was bg-background/80).
  • Header uses gap-2 and is no longer centered on mobile. Title is font-heading leading-none font-medium. Description styles links.
  • Close button is a 32px ghost icon button at top-4 end-4 with shadcn's hover, focus and press states.

Behavior and markup

  • Title renders <h2> (was <h3>).
  • The dialog gets aria-labelledby / aria-describedby from Title and Description, so it has the accessible name WAI-ARIA 1.2 requires. Attributes the author sets win.
  • The trigger's button gets aria-haspopup="dialog", aria-expanded and aria-controls.
  • Open and close animate for 100ms on the panel and the backdrop. The controller waits for the exit animation before calling close(). With prefers-reduced-motion it closes at once.
  • New show_close_button: on DialogContent (default true) and DialogFooter (default false, renders an outline Close button), matching shadcn's showCloseButton.

Changes existing apps will see

  • Default width goes from 512px (max-w-lg) to 448px (sm:max-w-md). Explicit size: values are unchanged.
  • Title is smaller and lighter, and the header is left-aligned on mobile.
  • max-h-screen is gone; the native <dialog> already limits its height and scrolls.
  • Button is now a generator dependency of Dialog.
  • Apps that copied the component need to regenerate the parts and the Stimulus controller together.

Audit files

  • audit/component_map.yml maps showCloseButton to show_close_button.
  • audit/divergences.yml registers the trigger wrapper <div>, since Phlex has no render prop.
BeforeAfter
(screenshot to add)(screenshot to add)

Testing instructions

  1. cd gem && bundle exec rake (tests + standardrb).
  2. cd docs && bin/dev, then open /docs/dialog:
    • Open each example in light and dark mode and check the text is readable in dark mode.
    • Esc, Cancel, the X and a backdrop click close the dialog with a short fade, and focus returns to the trigger.
    • Pressing on the panel padding, or dragging from inside the panel onto the backdrop, keeps it open (the #580 fix).
    • The "Close button" example has no X and shows a Close button in the footer.
  3. In a screen reader or the browser's accessibility tree, the dialog is announced as "dialog,
  4. With reduced motion on in the OS, the dialog opens and closes without animation.

šŸ¤– Generated with Claude Code


Summary by cubic

Brings Dialog in line with shadcn's Base UI + vega styling and behavior, fixing the shadcn-audit findings from #580. The panel and overlay match the vega look, the title renders as an <h2>, the dialog gets its accessible name from Title/Description, the trigger exposes dialog ARIA state, and open/close now animate with a 100ms exit that waits for the animation before closing. The trigger's <div> wrapper and its motion-reduce tokens are registered as accepted divergences.

Changes

  • Panel uses bg-popover text-popover-foreground ring-1 ring-foreground/10 rounded-xl gap-6 text-sm; overlay is bg-black/10 with backdrop-blur-xs.
  • Header is left-aligned on mobile; title uses font-heading leading-none font-medium; description styles links.
  • Close button is a 32px ghost icon button in the top-end corner with matching focus and press states.
  • New show_close_button: on DialogContent (default true) and DialogFooter (default false) to hide the corner X or add a footer Close button. Button is now a generator dependency of Dialog.
  • Controller sets aria-labelledby/aria-describedby and trigger aria-haspopup="dialog", aria-controls, and aria-expanded; author-set ids and ARIA are never overwritten, and the ARIA goes on the first interactive descendant of the trigger wrapper.
  • Closing (X, Cancel, Esc, backdrop) plays the exit animation before close(); prefers-reduced-motion closes at once.

Breaking changes for existing apps

  • Default width changes from 512px to 448px (sm:max-w-md); explicit size: values are unchanged.
  • Title is smaller and lighter; header is left-aligned on mobile.
  • max-h-screen is gone; the native <dialog> already limits height and scrolls.
  • Apps that copied the component need to regenerate the parts and the Stimulus controller together.