ComponentsCSS
Sheet
Floating drawer that slides in from the right, left or bottom, inset from the edges like an iOS sheet.
Installation
npx shadcn@latest add https://ui-collections.vercel.app/r/glass/sheet.jsonAlso installs the theme, cn() and dialog. Packages: clsx, tailwind-merge, radix-ui.
Usage
sheet-demo.tsx
"use client";
import { Button } from "@/registry/kit/ui/button";
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/kit/ui/sheet";
import { Switch } from "@/registry/kit/ui/switch";
const settings = [
{ id: "previews", label: "Preview deployments", on: true },
{ id: "comments", label: "Comments on previews", on: true },
{ id: "analytics", label: "Web analytics", on: false },
];
export default function SheetDemo() {
return (
<div className="flex flex-wrap justify-center gap-3">
{(["right", "bottom"] as const).map((side) => (
<Sheet key={side}>
<SheetTrigger asChild>
<Button>Open {side} sheet</Button>
</SheetTrigger>
<SheetContent side={side}>
<SheetHeader>
<SheetTitle>Project settings</SheetTitle>
<SheetDescription>Changes apply to the next deploy.</SheetDescription>
</SheetHeader>
<ul className="grid gap-1">
{settings.map((s) => (
<li key={s.id} className="flex items-center justify-between rounded-field px-1 py-3">
<label htmlFor={`${side}-${s.id}`} className="text-sm">
{s.label}
</label>
<Switch id={`${side}-${s.id}`} defaultChecked={s.on} />
</li>
))}
</ul>
<SheetFooter>
<SheetClose asChild>
<Button variant="solid">Done</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
))}
</div>
);
}API
<SheetContent side>= "right"
"right" | "left" | "bottom"Edge to slide from.
PartsTrigger, Header, Title, Description, Footer, CloseSame parts as Dialog.
Source
components/ui/sheet.tsx
"use client";
import { Dialog as DialogPrimitive } from "radix-ui";
import type { ComponentProps, HTMLAttributes } from "react";
import { cn } from "@/registry/kit/lib/utils";
import { DialogCloseButton, DialogOverlay } from "@/registry/kit/ui/dialog";
export {
Dialog as Sheet,
DialogClose as SheetClose,
DialogDescription as SheetDescription,
DialogTitle as SheetTitle,
DialogTrigger as SheetTrigger,
} from "@/registry/kit/ui/dialog";
const sides = {
right: "inset-y-3 right-3 w-[min(24rem,calc(100vw-1.5rem))] starting:translate-x-[105%]",
left: "inset-y-3 left-3 w-[min(24rem,calc(100vw-1.5rem))] starting:-translate-x-[105%]",
bottom: "inset-x-3 bottom-3 max-h-[85dvh] starting:translate-y-[105%]",
};
export interface SheetContentProps extends ComponentProps<typeof DialogPrimitive.Content> {
side?: keyof typeof sides;
bodyClassName?: string;
}
/** A floating surface drawer that slides in from an edge, inset from the viewport like an iOS sheet. */
export function SheetContent({ side = "right", className, bodyClassName, children, ...props }: SheetContentProps) {
return (
<DialogPrimitive.Portal>
<DialogOverlay />
<DialogPrimitive.Content
className={cn(
"surface-raised fixed z-50 flex flex-col overflow-hidden rounded-card text-fg outline-none",
"transition-[translate,opacity] duration-300 ease-theme starting:opacity-0 data-[state=closed]:animate-pop-out",
sides[side],
className,
)}
{...props}
>
<div className={cn("min-h-0 flex-1 overflow-y-auto overscroll-contain p-6 md:p-7", bodyClassName)}>{children}</div>
<DialogCloseButton />
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
);
}
export function SheetHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
return <div className={cn("mb-5 grid gap-2 pr-8", className)} {...props} />;
}
export function SheetFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
return <div className={cn("mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:flex-wrap sm:justify-end", className)} {...props} />;
}