Sheet
Sliding panel for dialogs, forms, and multi-step flows. Built on Base UI Dialog with animated edge placement.
Overview
The Sheet component presents content in a panel that slides from any edge of the viewport. It's ideal for lightweight flows such as settings, filters, or multi-step forms. Built on Base UI Dialog primitives with smooth edge-placement animations, sticky blurred chrome, and URL state support.
Usage
import {
Sheet,
SheetBody,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@tilt-legal/cubitt-components/sheet";<Sheet>
<SheetTrigger render={<Button />}>Open Sheet</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
<SheetDescription>Sheet description goes here.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>Using Cubitt's URL-state hooks, you can sync the sheet state with the URL by providing a paramName:
// Opens when ?settings=true in URL
<Sheet paramName="settings">
<SheetTrigger render={<Button />}>Open Settings</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Settings</SheetTitle>
<SheetDescription>
Configure your preferences.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
// Advanced options:
<Sheet
paramName="sheet"
paramClearOnDefault={true} // Remove param when closed
onUrlValueChange={(value) => console.log('Sheet open:', value)}
>
{/* ... */}
</Sheet>Examples
Basic
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>
Open Sheet
</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
<SheetDescription>
This is a basic sheet that slides in from the right.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>With Actions
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>
Open Sheet
</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Confirm Action</SheetTitle>
<SheetDescription>
Are you sure you want to proceed with this action? This cannot be
undone.
</SheetDescription>
</SheetHeader>
<SheetFooter>
<SheetClose>Cancel</SheetClose>
<Button>Continue</Button>
</SheetFooter>
</SheetContent>
</Sheet>With Form
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>
Edit Profile
</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Edit Profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you're done.
</SheetDescription>
</SheetHeader>
<SheetBody>
<Label>
Name
<Input defaultValue="John Doe" />
</Label>
<Label>
Email
<Input defaultValue="john@example.com" type="email" />
</Label>
</SheetBody>
<SheetFooter>
<SheetClose>Cancel</SheetClose>
<Button>Save Changes</Button>
</SheetFooter>
</SheetContent>
</Sheet>Different Sides
Sheets can slide in from any edge of the viewport.
// Left side
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>Left</SheetTrigger>
<SheetContent float side="left">
<SheetHeader>
<SheetTitle>Left Side</SheetTitle>
<SheetDescription>This sheet slides in from the left.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
// Right side (default)
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>Right</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Right Side</SheetTitle>
<SheetDescription>This sheet slides in from the right.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
// Top side
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>Top</SheetTrigger>
<SheetContent float side="top">
<SheetHeader>
<SheetTitle>Top Side</SheetTitle>
<SheetDescription>This sheet slides in from the top.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
// Bottom side
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>Bottom</SheetTrigger>
<SheetContent float side="bottom">
<SheetHeader>
<SheetTitle>Bottom Side</SheetTitle>
<SheetDescription>This sheet slides in from the bottom.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>Scrollable Content
Sheet with long content that scrolls. The header and footer remain sticky with a blurred background effect.
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>
Terms & Conditions
</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Terms and Conditions</SheetTitle>
<SheetDescription>
Please read and accept our terms and conditions.
</SheetDescription>
</SheetHeader>
<SheetBody className="space-y-4 text-sm text-fg-2">
{/* Long content... */}
</SheetBody>
<SheetFooter>
<SheetClose>Decline</SheetClose>
<Button>Accept</Button>
</SheetFooter>
</SheetContent>
</Sheet>The vertical scrollbar track is automatically inset to clear sticky sheet headers and footers. SheetHeader measures into --scrollbar-inset-top; SheetFooter measures into --scrollbar-inset-bottom.
Without Close Button
<Sheet>
<SheetTrigger render={<Button variant="secondary" />}>
Open Without Close Button
</SheetTrigger>
<SheetContent float showCloseButton={false} side="right">
<SheetHeader>
<SheetTitle>No Close Button</SheetTitle>
<SheetDescription>
This sheet does not have the X close button in the top right.
</SheetDescription>
</SheetHeader>
<SheetFooter>
<SheetClose>Close</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>URL State
Sheet with URL state synchronization for shareable sheet states.
import { Button } from "@tilt-legal/cubitt-components/button";
import {
Sheet,
SheetBody,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@tilt-legal/cubitt-components/sheet";
// Sheet state synced with ?settings=true
<Sheet paramName="settings" defaultOpen={false}>
<SheetTrigger render={<Button variant="secondary" />}>
Open with URL state
</SheetTrigger>
<SheetContent float side="right">
<SheetHeader>
<SheetTitle>Settings</SheetTitle>
<SheetDescription>
This sheet's state is synced with the URL. Check the URL parameter when
you open it!
</SheetDescription>
</SheetHeader>
<SheetFooter>
<SheetClose>Cancel</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>;API Reference
Sheet
Root component that manages sheet state.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state of the sheet. |
defaultOpen | boolean | false | Default open state for uncontrolled usage. |
onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
modal | boolean | true | Whether the sheet is modal (locks scroll). |
URL State Props
The following props enable URL state management by syncing the sheet open/closed state with URL parameters via TanStack Router search params. When paramName is provided, the sheet's state will be reflected in the URL.
| Prop | Type | Default | Description |
|---|---|---|---|
paramName | string | - | URL parameter name for syncing state with URL. When provided, enables URL state management. |
paramMatchValue | string | - | Value that must match for the sheet to open. |
onUrlValueChange | (value: boolean | null) => void | - | Callback when URL parameter value changes. |
paramClearOnDefault | boolean | true | Remove URL param when value equals default. |
paramThrottle | number | - | Throttle URL updates in milliseconds. |
paramDebounce | number | - | Debounce URL updates in milliseconds. |
SheetTrigger
Button that opens the sheet.
| Prop | Type | Default | Description |
|---|---|---|---|
render | React.ReactElement | - | Custom trigger element to render. |
SheetContent
Container for the sheet content with backdrop and animations.
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'top' | 'right' | 'bottom' | 'left' | 'right' | Which edge the sheet slides from. |
float | boolean | true | Renders content as an inset floating sheet with rounded corners. Set false for an edge-flush sheet. |
showCloseButton | boolean | true | Show a built-in close button in the corner. |
className | string | - | Additional CSS classes for the sheet popup. |
CSS Custom Properties
| Variable | Default | Description |
|---|---|---|
--scrollbar-inset-top | 1rem | Top margin of the vertical scrollbar track. |
--scrollbar-inset-bottom | 1rem | Bottom margin of the vertical scrollbar track. |
SheetHeader / SheetFooter
Container components for sheet header and footer with sticky positioning.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the header. |
SheetBody
Container for the main content area with consistent padding.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the body. |
SheetTitle
The accessible title of the sheet.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the title. |
SheetDescription
Description text that provides context about the sheet.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the description. |
SheetClose
Button that closes the sheet.
| Prop | Type | Default | Description |
|---|---|---|---|
render | React.ReactElement | - | Custom close button element to render. |
className | string | - | Additional CSS classes for the close button. |