Sheet
Slide-over panel built on Radix Dialog. Use it for side panels, filters, navigation, and contextual actions.
Side workflows without leaving contextLink to section
The Sheet component is a slide-over panel used for side workflows, filters, navigation, and contextual editing.
Use it for:
- advanced filters
- mobile navigation
- side editing panels
- detail panels
- contextual settings
- secondary workflows that should preserve page context
Sheet is built on Radix Dialog and styled with PyColors semantic tokens such as bg-card, border-border, and text-foreground.
Core idea
Sheet keeps the user in context while exposing a larger secondary workflow.
ImportLink to section
import { Sheet } from "@pycolors/ui";Basic usageLink to section
Use Sheet for a side panel that contains a focused but larger workflow than a Dialog.
"use client";
import {
Button,
Input,
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
} from "@pycolors/ui";
export function ProfileSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open sheet</Button>
</SheetTrigger>
<SheetContent side="right" className="w-[380px] sm:w-[420px]">
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Update your account details. Changes are saved on submit.
</SheetDescription>
</SheetHeader>
<div className="mt-6 grid gap-3">
<Input label="Name" placeholder="Patrice" />
<Input label="Email" placeholder="you@domain.com" />
<Input label="Company" placeholder="PyColors" autoComplete="organization" />
</div>
<SheetFooter className="mt-6">
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}Trigger rule
Prefer SheetTrigger asChild so the trigger keeps the semantics of the element you pass, such as a real Button.
When to use SheetLink to section
Use Sheet when the user needs more space than a menu, but should keep page context.
Filters
Use for advanced filters without crowding the toolbar.
Side editing
Use for editing records while keeping the parent page visible.
Mobile navigation
Use a left sheet for mobile menus and app navigation.
SidesLink to section
Sheets support four placement options through the side prop.
Use the placement to match the mental model of the workflow.
import {
Button,
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@pycolors/ui";
export function SheetSides() {
return (
<div className="flex flex-wrap gap-2">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Right</Button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Right sheet</SheetTitle>
<SheetDescription>
Default placement for editing and details.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Left</Button>
</SheetTrigger>
<SheetContent side="left">
<SheetHeader>
<SheetTitle>Left sheet</SheetTitle>
<SheetDescription>
Recommended for navigation.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
</div>
);
}Placement guidanceLink to section
| Side | Best for |
|---|---|
right | Editing, details, contextual panels |
left | Navigation, app menu, mobile sidebar |
top | Global filters, announcements, compact controls |
bottom | Mobile-first actions, quick controls |
Placement rule
Use right for editing, left for navigation, and bottom for mobile-first action panels.
Close behaviorLink to section
SheetContent includes a close button. Use SheetClose for explicit footer actions.
import {
Button,
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
SheetClose,
} from "@pycolors/ui";
export function SheetCloseActions() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Confirm</SheetTitle>
<SheetDescription>
Close with the button, the close control, or Escape.
</SheetDescription>
</SheetHeader>
<div className="mt-6 flex gap-2">
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<SheetClose asChild>
<Button>Done</Button>
</SheetClose>
</div>
</SheetContent>
</Sheet>
);
}Usage patternsLink to section
Choose the right sideLink to section
Pick the placement based on the workflow: right for details, left for navigation, bottom for mobile actions.
Keep the page context visibleLink to section
A sheet should extend the page, not replace it. Avoid using it for flows that need a full page.
Provide title and descriptionLink to section
Use SheetTitle and SheetDescription so users understand the purpose of the panel.
Use one primary actionLink to section
Most sheets should have one primary action and one secondary close or cancel action.
Handle scroll intentionallyLink to section
Avoid nested scroll containers. Prefer one clear scrollable region inside the sheet.
Sheet vs Dialog vs Dropdown MenuLink to section
| Situation | Use |
|---|---|
| Advanced filters | Sheet |
| Side editing panel | Sheet |
| Mobile navigation | Sheet |
| Record details panel | Sheet |
| Short destructive confirmation | Dialog |
| Small contextual action list | DropdownMenu |
| Full multi-step workflow | Page |
Decision rule
Sheet is for side workflows. Dialog is for focused decisions. Dropdown Menu is for compact choices.
APILink to section
ComponentsLink to section
| Component | Description |
|---|---|
Sheet | Root container |
SheetTrigger | Trigger element, usually with asChild |
SheetClose | Close control, usually wrapping a Button |
SheetContent | Slide-over panel surface |
SheetHeader | Title and description stack helper |
SheetFooter | Action row helper |
SheetTitle | Accessible sheet title |
SheetDescription | Supporting text |
SheetContent propsLink to section
| Prop | Type | Default | Description |
|---|---|---|---|
side | SheetSide | "right" | Placement of the panel |
className | string | — | Additional Tailwind classes |
...props | DialogPrimitive.Content props | — | Radix Dialog Content props |
Sheet, SheetTrigger, and SheetClose are re-exports of Radix Dialog primitives and accept their native props.
TypeScript typesLink to section
export type SheetSide = "top" | "bottom" | "left" | "right";
export interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
side?: SheetSide;
}AccessibilityLink to section
Sheet is built on Radix Dialog primitives.
That means:
- focus is trapped while open
- focus returns to the trigger on close
- Escape closes the sheet by default
- screen readers receive dialog semantics
SheetTitleprovides the accessible name
Guidelines:
- use
SheetTrigger asChildwith a semantic trigger - include a clear
SheetTitle - include a useful
SheetDescription - avoid using Sheet for destructive confirmations
- avoid nested sheets
- avoid long multi-step flows inside a sheet
Accessibility rule
A sheet still behaves like a dialog. It needs clear labeling, focus management, and a bounded task.
Prefer / avoidLink to section
Prefer
- right sheets for editing and details
- left sheets for navigation
- clear title and description
- one primary footer action
- one intentional scroll region
Avoid
- destructive confirmations that should be dialogs
- full-page workflows squeezed into a panel
- nested sheets
- multiple competing primary actions
- unclear scrolling with nested containers
Product copy guidelinesLink to section
Sheet copy should tell users what the panel does and what happens next.
Filter projects
Narrow results by status, owner, date, and plan.
Edit profile
Update account details. Changes are saved when you submit.