Responsive Dialog
One overlay API — a centred Dialog from 768px up, a bottom sheet with a drag handle below it.
- Status
- Since
0.23.116- Accessibility pattern
- dialog in both forms; focus trapped; Esc closes
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Responsive Dialog before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify responsive-dialogInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/responsive-dialogRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/dialog, @vegastack/sheet, @vegastack/use-mobile.
Usage
import {
ResponsiveDialog,
ResponsiveDialogBody,
ResponsiveDialogContent,
ResponsiveDialogFooter,
ResponsiveDialogHeader,
ResponsiveDialogTitle,
} from "@/components/ui/responsive-dialog";
<ResponsiveDialog open={open} onOpenChange={setOpen}>
<ResponsiveDialogContent size="md">
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Members · Product</ResponsiveDialogTitle>
</ResponsiveDialogHeader>
<ResponsiveDialogBody>{list}</ResponsiveDialogBody>
<ResponsiveDialogFooter>
<Button>Add</Button>
</ResponsiveDialogFooter>
</ResponsiveDialogContent>
</ResponsiveDialog>;Write the dialog once. From 768px up it is a Dialog of the size you pass (md for share,
members and settings dialogs); below that it is a bottom Sheet: full width, as tall as its content
up to 85% of the screen, a drag handle at the top, the same header and body, and a footer that stays
at the bottom and clears the home indicator. The consumer writes no useIsMobile branch and no
layout code. The form follows the viewport while the dialog is closed and is locked while it is
open: resizing across the breakpoint never closes it or resets what is inside it, and the other
form applies the next time it opens.
Scope
- Owns: the switch between
Dialogand a bottomSheet, the handle and the safe-area footer. - Does not own: the dialog's content, its sizes (they are
Dialog's) or the sheet's motion (it isSheet's). - Compose with: anything a
Dialogholds —share-01uses it for Share.
Anatomy
Examples
Sizes
size sizes the dialog form only; the phone sheet is always full width.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
breakpoint | number | 768 | Viewport width (px) at and above which it is a Dialog; below it, a bottom Sheet. |
ResponsiveDialogContent takes DialogContent's props (size, showCloseButton, closeLabel).
ResponsiveDialogTrigger, ResponsiveDialogClose, ResponsiveDialogHeader,
ResponsiveDialogTitle, ResponsiveDialogDescription, ResponsiveDialogBody and
ResponsiveDialogFooter add no props of their own — each takes its Dialog part's props.
Accessibility
- Both forms are Base UI Dialog:
role="dialog"witharia-modal, named by the title, focus trapped inside and returned to the trigger. - The drag handle is decorative (
aria-hidden); the sheet closes with Escape, its × or a Close.
| Key | Action |
|---|---|
| Tab | Move between controls inside |
| Esc | Close and return focus to the opener |
| Contract | States tested |
|---|---|
| Behaviour | closed, open, dialog, sheet, escape-closes |
| Accessibility | labeled, keyboard, browser-accessibility-test |
| Visual | default, hover, focus |