Responsive Dialog

A modal that adapts to the viewport — a centered Dialog on desktop, a bottom Drawer on mobile, from one shared set of components so you don't maintain two versions of the same form.

Last updated August 8, 2026

CreditsModifiedPublished

Copied from shadcn/ui.

  • Not a shadcn/ui component — this composition (switch between Dialog and Drawer by viewport width, behind one shared API) is original to this registry, built on top of this registry's own Dialog and Drawer
  • ResponsiveDialogDesktopOnly hides Cancel/انصراف-style actions on mobile, where the drawer is already dismissed by swiping down, so a redundant Cancel button never has to be built twice

Overview

Resize the browser window to see the preview switch between a Dialog and a Drawer.

"use client"
 
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  ResponsiveDialog,
  ResponsiveDialogClose,
  ResponsiveDialogDescription,
  ResponsiveDialogDesktopOnly,
  ResponsiveDialogFooter,
  ResponsiveDialogHeader,
  ResponsiveDialogPanel,
  ResponsiveDialogPopup,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@/components/ui/responsive-dialog"
 
export function ResponsiveDialogDemoExample() {
  return (
    <ResponsiveDialog>
      <ResponsiveDialogTrigger
        render={<Button variant="outline">Edit profile</Button>}
      />
      <ResponsiveDialogPopup>
        <ResponsiveDialogHeader>
          <ResponsiveDialogTitle>Edit profile</ResponsiveDialogTitle>
          <ResponsiveDialogDescription>
            A Dialog on desktop, a bottom Drawer on mobile — resize the window
            to see it switch.
          </ResponsiveDialogDescription>
        </ResponsiveDialogHeader>
        <ResponsiveDialogPanel className="flex flex-col gap-4">
          <div className="flex flex-col gap-2">
            <Label htmlFor="responsive-dialog-demo-name">Name</Label>
            <Input
              id="responsive-dialog-demo-name"
              defaultValue="Sadegh Alavi"
            />
          </div>
        </ResponsiveDialogPanel>
        <ResponsiveDialogFooter>
          <Button type="submit">Save changes</Button>
          <ResponsiveDialogDesktopOnly>
            <ResponsiveDialogClose
              render={<Button variant="outline">Cancel</Button>}
            />
          </ResponsiveDialogDesktopOnly>
        </ResponsiveDialogFooter>
      </ResponsiveDialogPopup>
    </ResponsiveDialog>
  )
}

ResponsiveDialogFooter is optional here too — for a purely informational modal, skip it on both the Dialog and Drawer side.

import { BadgeCheckIcon } from "lucide-react"
 
import { Button } from "@/components/ui/button"
import {
  ResponsiveDialog,
  ResponsiveDialogDescription,
  ResponsiveDialogHeader,
  ResponsiveDialogPanel,
  ResponsiveDialogPopup,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@/components/ui/responsive-dialog"
 
export function ResponsiveDialogNoFooterExample() {
  return (
    <ResponsiveDialog>
      <ResponsiveDialogTrigger
        render={<Button variant="outline">View receipt</Button>}
      />
      <ResponsiveDialogPopup>
        <ResponsiveDialogHeader>
          <ResponsiveDialogTitle>Payment successful</ResponsiveDialogTitle>
          <ResponsiveDialogDescription>
            Your subscription is active. A receipt was sent to your email.
          </ResponsiveDialogDescription>
        </ResponsiveDialogHeader>
        <ResponsiveDialogPanel className="flex items-center gap-3">
          <BadgeCheckIcon className="size-8 shrink-0 text-primary" />
          <p className="text-sm text-muted-foreground">
            Nothing left to do here — dismiss with Escape, a tap outside, or (on
            mobile) a swipe down.
          </p>
        </ResponsiveDialogPanel>
      </ResponsiveDialogPopup>
    </ResponsiveDialog>
  )
}

Installation

$ npx shadcn@latest add https://ui.persian-labs.ir/r/responsive-dialog.json

Usage

import {
  ResponsiveDialog,
  ResponsiveDialogClose,
  ResponsiveDialogDescription,
  ResponsiveDialogDesktopOnly,
  ResponsiveDialogFooter,
  ResponsiveDialogHeader,
  ResponsiveDialogPanel,
  ResponsiveDialogPopup,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@/components/ui/responsive-dialog"
 
export function Example() {
  return (
    <ResponsiveDialog>
      <ResponsiveDialogTrigger render={<Button variant="outline">Open</Button>} />
      <ResponsiveDialogPopup>
        <ResponsiveDialogHeader>
          <ResponsiveDialogTitle>Edit profile</ResponsiveDialogTitle>
          <ResponsiveDialogDescription>
            A Dialog on desktop, a Drawer on mobile.
          </ResponsiveDialogDescription>
        </ResponsiveDialogHeader>
        <ResponsiveDialogPanel>{/* form fields */}</ResponsiveDialogPanel>
        <ResponsiveDialogFooter>
          <Button type="submit">Save changes</Button>
          {/* Skipped on mobile — the drawer is dismissed by swiping. */}
          <ResponsiveDialogDesktopOnly>
            <ResponsiveDialogClose render={<Button variant="outline">Cancel</Button>} />
          </ResponsiveDialogDesktopOnly>
        </ResponsiveDialogFooter>
      </ResponsiveDialogPopup>
    </ResponsiveDialog>
  )
}

RTL

"use client"
 
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  ResponsiveDialog,
  ResponsiveDialogClose,
  ResponsiveDialogDescription,
  ResponsiveDialogDesktopOnly,
  ResponsiveDialogFooter,
  ResponsiveDialogHeader,
  ResponsiveDialogPanel,
  ResponsiveDialogPopup,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@/components/ui/responsive-dialog"
 
export function ResponsiveDialogRtlExample() {
  return (
    <ResponsiveDialog drawerPosition="bottom">
      <ResponsiveDialogTrigger
        render={<Button variant="outline">ویرایش پروفایل</Button>}
      />
      <ResponsiveDialogPopup>
        <ResponsiveDialogHeader>
          <ResponsiveDialogTitle>ویرایش پروفایل</ResponsiveDialogTitle>
          <ResponsiveDialogDescription>
            در دسکتاپ یک Dialog و در موبایل یک کشوی پایینی نمایش داده می‌شود.
          </ResponsiveDialogDescription>
        </ResponsiveDialogHeader>
        <ResponsiveDialogPanel className="flex flex-col gap-4">
          <div className="flex flex-col gap-2">
            <Label htmlFor="responsive-dialog-rtl-name">نام</Label>
            <Input id="responsive-dialog-rtl-name" defaultValue="صادق علوی" />
          </div>
        </ResponsiveDialogPanel>
        <ResponsiveDialogFooter>
          <Button type="submit">ذخیره تغییرات</Button>
          <ResponsiveDialogDesktopOnly>
            <ResponsiveDialogClose
              render={<Button variant="outline">انصراف</Button>}
            />
          </ResponsiveDialogDesktopOnly>
        </ResponsiveDialogFooter>
      </ResponsiveDialogPopup>
    </ResponsiveDialog>
  )
}

API Reference

ResponsiveDialog