Sheet
Edge-anchored sliding panel. Opens from any side.
Preview
Code
import { Button } from "@/components/ui/button";
import {
Sheet, SheetBody, SheetContent, SheetDescription,
SheetFooter, SheetHeader, SheetTitle, SheetTrigger,
} from "@/components/ui/sheet";
export function Example() {
return (
<div className="flex gap-3">
{/* Left — navigation drawer */}
<Sheet>
<SheetTrigger render={<Button variant="outline">Open from left</Button>} />
<SheetContent side="left">
<SheetHeader>
<SheetTitle>Navigation</SheetTitle>
</SheetHeader>
<SheetBody>
<nav>{/* links */}</nav>
</SheetBody>
</SheetContent>
</Sheet>
{/* Right — form / settings panel */}
<Sheet>
<SheetTrigger render={<Button variant="outline">Open from right</Button>} />
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Update your details and save.</SheetDescription>
</SheetHeader>
<SheetBody>{/* form fields */}</SheetBody>
<SheetFooter>
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled open state. |
| defaultOpen | boolean | — | Uncontrolled initial open state. |
| onOpenChange | (open: boolean) => void | — | Called when the open state changes. |
Sub-components
SheetTrigger— Element that opens the sheet. Supports `render` for asChild pattern.SheetContent— The sliding panel. Includes backdrop.| Prop | Type | Default | Description |
|---|---|---|---|
| side | "left" | "right" | "top" | "bottom" | "right" | Which edge the sheet slides from. |
| variant | "solid" | "glass" | "solid" | Surface treatment. glass enables liquid-glass refraction (C3 themes only). |
| showCloseButton | boolean | true | Render the X close button. |
SheetHeader— Title + description region.SheetTitle— Required for accessibility.SheetDescription— Description text.SheetBody— Scrollable middle region.SheetFooter— Actions row at the bottom.SheetClose— Explicit close trigger.Built on Base UI Dialog. Supports all four edges. Replaces what was originally a separate Drawer component.
All components also accept standard HTML attributes for their root element (e.g. `id`, `aria-*`, `data-*`, event handlers) and forward `ref` where applicable.
Source
The full implementation of components/ui/sheet.tsx is below — copy it into your project and own it. Some components import the cn helper from lib/utils or other primitives; copy those too.
"use client";
import { cn } from "@/lib/utils";
import { Dialog as BaseDialog } from "@base-ui-components/react/dialog";
import { X } from "lucide-react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export const Sheet = BaseDialog.Root;
export const SheetTrigger = BaseDialog.Trigger;
export const SheetClose = BaseDialog.Close;
const sideClasses = {
left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm data-[starting-style]:-translate-x-full data-[ending-style]:-translate-x-full",
right:
"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm data-[starting-style]:translate-x-full data-[ending-style]:translate-x-full",
top: "inset-x-0 top-0 w-full border-b data-[starting-style]:-translate-y-full data-[ending-style]:-translate-y-full",
bottom:
"inset-x-0 bottom-0 w-full border-t data-[starting-style]:translate-y-full data-[ending-style]:translate-y-full",
} as const;
export function SheetContent({
className,
children,
side = "right",
variant = "solid",
showCloseButton = true,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Popup> & {
side?: keyof typeof sideClasses;
variant?: "solid" | "glass";
showCloseButton?: boolean;
children?: ReactNode;
}) {
return (
<BaseDialog.Portal>
<BaseDialog.Backdrop
className={cn(
"fixed inset-0 z-50 bg-bg/60 backdrop-blur-sm",
"data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
"transition-opacity duration-300",
)}
/>
<BaseDialog.Popup
className={cn(
"fixed z-50 flex flex-col shadow-lg border-border",
"transition-transform duration-300",
variant === "glass" ? "surface-glass-panel" : "bg-surface",
sideClasses[side],
className,
)}
{...props}
>
{children}
{showCloseButton && (
<BaseDialog.Close
className={cn(
"absolute right-4 top-4 rounded-sm text-fg-muted opacity-70 transition-opacity",
"hover:opacity-100 focus-visible:outline-1 focus-visible:outline-offset-0 focus-visible:outline-accent",
)}
aria-label="Close"
>
<X className="h-4 w-4" />
</BaseDialog.Close>
)}
</BaseDialog.Popup>
</BaseDialog.Portal>
);
}
export function SheetHeader({ className, ...props }: ComponentPropsWithoutRef<"div">) {
return (
<div
className={cn("flex flex-col gap-1.5 border-b border-border px-6 py-4", className)}
{...props}
/>
);
}
export function SheetTitle({
className,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Title>) {
return (
<BaseDialog.Title
className={cn("text-lg font-semibold leading-none tracking-tight text-fg", className)}
{...props}
/>
);
}
export function SheetDescription({
className,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Description>) {
return <BaseDialog.Description className={cn("text-sm text-fg-muted", className)} {...props} />;
}
export function SheetBody({ className, ...props }: ComponentPropsWithoutRef<"div">) {
return <div className={cn("flex-1 overflow-y-auto px-6 py-4", className)} {...props} />;
}
export function SheetFooter({ className, ...props }: ComponentPropsWithoutRef<"div">) {
return (
<div
className={cn(
"flex items-center gap-2 border-t border-border px-6 py-4 sm:justify-end",
className,
)}
{...props}
/>
);
}JavaFX
Ships in the PRIZM JavaFX library for thick-client C3 apps as PrizmSheet (extends (controller; overlays a StackPane)). Run the gallery to see it natively.
import design.prizm.fx.controls.PrizmSheet;
PrizmSheet(Side side)
PrizmSheet(Side side, Node content)| Prop | Type | Default | Description |
|---|---|---|---|
| setContent | (Node) → void | — | The panel body; compose it yourself. |
| setSize | (double) → void | — | Panel width (LEFT/RIGHT) or height (TOP/BOTTOM) in px. Default 360 / 280. |
| show | (StackPane host) → void | — | Overlay on a full-size host (e.g. the scene root wrapped in a StackPane) and slide in. |
| hide | () → void | — | Slide out and remove from the host (also on scrim click). |
No stock JavaFX equivalent — a controller that overlays a host StackPane with a scrim + a panel sliding from the chosen Side (200ms). Styled by the .prizm-sheet / .prizm-sheet-scrim rules. Mirrors components/ui/sheet.tsx (which covers all four sides).