Components
Loading preview...
This smart Modal component auto-switches between a Dialog and Drawer based on screen size using context. It gives you a seamless, unified API for building mobile-friendly popups, side panels, or modals with full Radix/vaul support—super handy for responsive UIs.
npx shadcn@latest add https://21st.dev/r/sshahaider/modal'use client';
import React from 'react';
import {
Modal,
ModalBody,
ModalClose,
ModalContent,
ModalDescription,
ModalFooter,
ModalHeader,
ModalTitle,
ModalTrigger,
} from '@/components/ui/modal';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useIsMobile } from '@/hooks/use-media-query';
function Preview() {
const [open, setOpen] = React.useState(false);
return (
<Modal open={open} onOpenChange={setOpen}>
<ModalTrigger>
<Button variant="outline">Open Modal</Button>
</ModalTrigger>
<ModalContent>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
>
<ModalHeader>
<ModalTitle>Edit Profile</ModalTitle>
<ModalDescription>Update your name and username.</ModalDescription>
</ModalHeader>
<ModalBody className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input id="name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-2">
<Label htmlFor="username">Username</Label>
<Input id="username" defaultValue="@peduarte" />
</div>
</ModalBody>
<ModalFooter>
<ModalClose asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</ModalClose>
<Button type="submit">Save</Button>
</ModalFooter>
</form>
</ModalContent>
</Modal>
);
}
export default function Default() {
return (
<div className="flex min-h-screen flex-col items-center space-y-12 py-24">
<DeviceIndicator />
<div className="space-y-3 text-center">
<h1 className="text-6xl font-extrabold tracking-tight">
Responsive Modal
</h1>
<p className="text-xl font-medium">
This modal automatically adapts to your device:
</p>
<div className="flex justify-center gap-8 text-sm">
<div className="flex items-center gap-2 rounded-md border p-1">
<div className="h-3 w-3 rounded-full bg-blue-500" />
<span className="flex items-center gap-2">
<strong className="font-semibold">Desktop:</strong>{' '}
<code>
<pre>{`<Dialog />`}</pre>
</code>
</span>
</div>
<div className="flex items-center gap-2 rounded-md border p-1">
<div className="h-3 w-3 rounded-full bg-green-500" />
<span className="flex items-center gap-2">
<strong className="font-semibold">Mobile:</strong>{' '}
<code>
<pre>{`<Drawer />`}</pre>
</code>
</span>
</div>
</div>
</div>
<Preview />
<p className="text-muted-foreground text-sm">
*Try resizing your browser window or opening this on different devices!
</p>
</div>
);
}
function DeviceIndicator() {
const isMobile = useIsMobile();
return (
<div className="fixed top-4 left-4 z-50">
<div
className={`bg-muted rounded-full border px-3 py-2 text-sm font-medium`}
>
<div className="flex items-center gap-2">
<div
className={`h-2 w-2 rounded-full ${isMobile ? 'bg-green-500' : 'bg-blue-500'}`}
/>
{isMobile ? 'Mobile' : 'Desktop'}
</div>
</div>
</div>
);
}