Sheet
An edge-anchored tray that slides in from any side and hovers above the page with a live penumbra.
@emboss/sheet
Installation
pnpm dlx shadcn@latest add @emboss/sheetUsage
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
export function Example() {
return (
<Sheet>
<SheetTrigger render={<Button>Open</Button>} />
<SheetContent side="right">
<SheetTitle>Patch bay</SheetTitle>
<SheetDescription>Route signals between modules.</SheetDescription>
</SheetContent>
</Sheet>
);
}API reference
Sheet / SheetTrigger / SheetContent
Root state, trigger, and the edge tray. SheetTitle and SheetDescription label the tray.
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "top" | "right" | "bottom" | "left" | "right" | Which edge the tray is anchored to (SheetContent). |
| open / defaultOpen / onOpenChange | boolean / boolean / (open) => void | — | Controlled and uncontrolled open state. |
Keyboard
| Keys | Action |
|---|---|
| Esc | Closes the sheet and returns focus to the trigger. |
Source
View source — sheet.tsxHide source — sheet.tsx
"use client";
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const sheetVariants = cva(
"fixed z-50 flex flex-col gap-4 bg-surface-2 p-6 text-ink shadow-float-2 transition-[translate,opacity] duration-(--duration-float) ease-(--ease-float)",
{
variants: {
side: {
right:
"inset-y-0 right-0 w-80 max-w-[85vw] data-ending-style:translate-x-full data-starting-style:translate-x-full",
left: "inset-y-0 left-0 w-80 max-w-[85vw] data-ending-style:-translate-x-full data-starting-style:-translate-x-full",
top: "inset-x-0 top-0 max-h-[85vh] data-ending-style:-translate-y-full data-starting-style:-translate-y-full",
bottom:
"inset-x-0 bottom-0 max-h-[85vh] rounded-t-lg data-ending-style:translate-y-full data-starting-style:translate-y-full",
},
},
defaultVariants: { side: "right" },
},
);
/**
* An edge-anchored tray that slides in over the page — the page keeps a
* live shadow under it, so it reads as hovering, not painted on.
*
* @example
* <Sheet>
* <SheetTrigger render={<Button>Open patch bay</Button>} />
* <SheetContent side="right">
* <SheetTitle>Patch bay</SheetTitle>
* </SheetContent>
* </Sheet>
*/
function Sheet(props: React.ComponentProps<typeof BaseDrawer.Root>) {
return <BaseDrawer.Root {...props} />;
}
function SheetTrigger(props: React.ComponentProps<typeof BaseDrawer.Trigger>) {
return <BaseDrawer.Trigger data-slot="sheet-trigger" {...props} />;
}
function SheetContent({
className,
side,
children,
...props
}: React.ComponentProps<typeof BaseDrawer.Popup> &
VariantProps<typeof sheetVariants>) {
return (
<BaseDrawer.Portal>
<BaseDrawer.Backdrop
data-slot="sheet-backdrop"
className="fixed inset-0 z-50 bg-ink/40 transition-opacity duration-(--duration-float) ease-(--ease-float) data-ending-style:opacity-0 data-starting-style:opacity-0"
/>
<BaseDrawer.Popup
data-slot="sheet-content"
className={cn(sheetVariants({ side }), className)}
{...props}
>
<BaseDrawer.Content className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto">
{children}
</BaseDrawer.Content>
<BaseDrawer.Close
aria-label="Close"
data-slot="sheet-close-corner"
className="absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-sm text-ink-faint focus-ring transition-[box-shadow,color] hover:text-ink hover:shadow-flush"
>
<X className="size-4" aria-hidden />
</BaseDrawer.Close>
</BaseDrawer.Popup>
</BaseDrawer.Portal>
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5", className)}
{...props}
/>
);
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof BaseDrawer.Title>) {
return (
<BaseDrawer.Title
data-slot="sheet-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof BaseDrawer.Description>) {
return (
<BaseDrawer.Description
data-slot="sheet-description"
className={cn("text-sm text-ink-muted", className)}
{...props}
/>
);
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex justify-end gap-2", className)}
{...props}
/>
);
}
function SheetClose(props: React.ComponentProps<typeof BaseDrawer.Close>) {
return <BaseDrawer.Close data-slot="sheet-close" {...props} />;
}
export {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
SheetFooter,
SheetClose,
};