Usage
import { ResponsiveDialog } from "@voila.dev/ui/responsive-dialog";<ResponsiveDialog.Root>
<ResponsiveDialog.Trigger render={<Button variant="outline" />}>Open</ResponsiveDialog.Trigger>
<ResponsiveDialog.Content>
<ResponsiveDialog.Header>
<ResponsiveDialog.Title>Cancel this project?</ResponsiveDialog.Title>
</ResponsiveDialog.Header>
<ResponsiveDialog.Body>…</ResponsiveDialog.Body>
<ResponsiveDialog.Footer>
<Button variant="destructive">Cancel project</Button>
</ResponsiveDialog.Footer>
</ResponsiveDialog.Content>
</ResponsiveDialog.Root>A Dialog on desktop and a bottom Drawer below 768px, behind one set of parts. A centred modal on a phone is the wrong shape — it fights the keyboard and puts its buttons where thumbs are not — so this switches rather than scaling. Use it for anything a phone user will meet; keep plain Dialog for desktop-only surfaces like an admin console.
Recipes
The switch happens at 768px. Open the preview above and narrow the window to see it.
The root owns the open state deliberately: the two halves are different component trees, so uncontrolled state would reset when the viewport crosses the breakpoint mid-interaction.
autoFocus={false} is worth knowing about when the first field is a text input.
Focusing it on open raises the keyboard over the content the user just asked to
see; the content stays reachable by tab either way.
API
ResponsiveDialog.Root
| Prop | Type | Default | |
|---|---|---|---|
children | ReactNode | — | The trigger and the content. |
defaultOpen | boolean | false | Whether it starts open, when uncontrolled. |
onOpenChange | ((open: boolean) => void) | — | Called when the overlay opens or closes, on either surface. |
open | boolean | — | Controlled open state. The root owns it so it survives crossing the breakpoint. |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-root.tsx.
ResponsiveDialog.Body
Plus every <div> prop. Source: responsive-dialog/components/responsive-dialog-body.tsx.
ResponsiveDialog.Close
| Prop | Type | Default | |
|---|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Element form only — the drawer half clones it, so render functions are unsupported. |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-close.tsx.
ResponsiveDialog.Content
| Prop | Type | Default | |
|---|---|---|---|
autoFocus | boolean | true | Whether opening moves focus into the content (the default). Pass false
when the first field is a text input you don't want to focus on open —
on a phone that focus opens the keyboard over the content you just asked to
see. The content stays reachable by tab either way. |
children | ReactNode | — | Header, body and footer parts. |
className | string | — | Classes for the panel on both surfaces. |
closeButtonLabel | string | "Close" | Accessible name for that X. This package ships no translations. |
overlayClassName | string | — | Classes for the backdrop behind the panel. className styles the panel itself. |
showCloseButton | boolean | true | Draws the X in the corner. |
size | DialogContentSize | "sm" | Desktop dialog width — the mobile drawer is always full-width. |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-content.tsx.
ResponsiveDialog.Description
| Prop | Type | Default | |
|---|---|---|---|
asChild | boolean | — |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-description.tsx.
ResponsiveDialog.Footer
| Prop | Type | Default | |
|---|---|---|---|
closeLabel | string | — | Text of the built-in dismiss button. Omit the prop to render no dismiss at all. |
Plus every <div> prop. Source: responsive-dialog/components/responsive-dialog-footer.tsx.
ResponsiveDialog.Header
Plus every <div> prop. Source: responsive-dialog/components/responsive-dialog-header.tsx.
ResponsiveDialog.Title
| Prop | Type | Default | |
|---|---|---|---|
asChild | boolean | — |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-title.tsx.
ResponsiveDialog.Trigger
| Prop | Type | Default | |
|---|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Element form only — the drawer half clones it, so render functions are unsupported. |
Plus the DOM props of the element it renders. Source: responsive-dialog/components/responsive-dialog-trigger.tsx.