Components
Sheet
Radix Dialog as an edge-anchored panel — a frosted acrylic surface that slides in over a blurred overlay.
A Radix Dialog
dressed as a slide-in panel: a translucent acrylic surface anchored to any edge,
over an 8px-blur overlay. Drop-in API-compatible with the shadcn Sheet — pass
side to choose which edge it enters from.
Installation
npx shadcn add https://acrylic-ui.vercel.app/r/sheet.jsonUsage
"use client"import { Button } from "@/registry/acrylic/button"import { Input } from "@/registry/acrylic/input"import { Field, FieldGroup, FieldLabel } from "@/registry/acrylic/field"import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from "@/registry/acrylic/sheet"// Faithful to shadcn's official Sheet demo: an "Edit profile" sheet sliding in// from the right. SheetContent only has gap-4, so the form body gets its own// horizontal padding (px-4) to align with the header/footer.export default function SheetDemo() { return ( <Sheet> <SheetTrigger asChild> <Button variant="neutral">Open</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Edit profile</SheetTitle> <SheetDescription> Make changes to your profile here. Click save when you're done. </SheetDescription> </SheetHeader> <FieldGroup variant="aligned" className="px-4 text-foreground"> <Field> <FieldLabel htmlFor="sheet-demo-name">Name</FieldLabel> <Input id="sheet-demo-name" defaultValue="Pedro Duarte" /> </Field> <Field> <FieldLabel htmlFor="sheet-demo-username">Username</FieldLabel> <Input id="sheet-demo-username" defaultValue="@peduarte" /> </Field> </FieldGroup> <SheetFooter> <Button type="submit">Save changes</Button> <SheetClose asChild> <Button variant="neutral">Close</Button> </SheetClose> </SheetFooter> </SheetContent> </Sheet> )}import { Button } from "@/registry/acrylic/button"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/acrylic/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="neutral">Open</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here.</SheetDescription>
</SheetHeader>
{/* form body — add px-4, SheetContent only sets gap-4 */}
<SheetFooter>
<Button type="submit">Save changes</Button>
<SheetClose asChild>
<Button variant="neutral">Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>Sides
The side prop on SheetContent anchors the panel to any edge —
top · right (default) · bottom · left — each with its own slide-in
direction.
"use client"import { Button } from "@/registry/acrylic/button"import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger,} from "@/registry/acrylic/sheet"// Faithful to shadcn's "sides" example: the same Sheet opening from each edge via// the `side` prop (top / right / bottom / left). A flex grid of triggers, each// wired to the matching side.const SIDES = ["top", "right", "bottom", "left"] as constexport default function SheetShowcase() { return ( <div className="flex flex-wrap gap-2"> {SIDES.map((side) => ( <Sheet key={side}> <SheetTrigger asChild> <Button variant="neutral" className="capitalize"> {side} </Button> </SheetTrigger> <SheetContent side={side}> <SheetHeader> <SheetTitle className="capitalize">{side} sheet</SheetTitle> <SheetDescription> Slides in from the {side} edge. </SheetDescription> </SheetHeader> </SheetContent> </Sheet> ))} </div> )}Notes
- Built on
@radix-ui/react-dialog, the same primitive as Dialog, so focus trapping, scroll locking, andEscape-to-close come for free. - Exports
Sheet,SheetTrigger,SheetClose,SheetContent,SheetHeader,SheetFooter,SheetTitle, andSheetDescription— a faithful mirror of the shadcn API. - The panel is the frosted acrylic material (translucent tint +
backdrop-blur) over an 8px-blur overlay; a built-in X sits in the top-right corner. SheetContentonly setsgap-4, so give any form body its own horizontal padding (e.g.px-4) to align it with the header and footer.- Drag-to-dismiss is a touch affordance — a mouse cannot drag the panel. With a
cursor, press-and-move already belongs to the browser (selecting text, scrolling);
taking it made the sheet slide away while the user was highlighting a sentence, and
no amount of arbitration fixed that reliably — at the few pixels where a drag has to
commit, the selection has not formed yet, and committing captures the pointer, which
kills it. Cursors close via the ✕,
Escape, or the overlay. - A top/bottom sheet needs its scrollable body in a nested container — put
overflow-y-autoon a child, never onSheetContentitself.touch-actionis read from the scroll container that implements the pan, so a nested child scrolls natively; on the panel it makes the panel that container, whosetouch-actionforbids the very axis it needs, and the body cannot be scrolled by finger at all. - Content that owns its own pointer gestures (a slider, a canvas) can opt out of the
drag entirely with
data-acr-no-dragon it or any ancestor inside the panel.