Components · Overlays
Sheet
BetaA side panel for secondary tasks, such as filters.
- Source
components/ui/sheet.tsx - shadcn/ui Sheet
- Figma: not linked yet (Q13)
When to use
- For a secondary task that relates to the page behind it: filtering the client list, quick-editing a client, reviewing a payout.
- When the coach benefits from seeing the page while working, and the task needs more room than a dialog.
When not to use
- For a short, focused task: use a Dialog.
- To confirm a destructive action: use an Alert dialog.
- For a panel opened from the bottom on a phone, with swipe to close: use a Drawer.
- For the main task of a page, or long forms with several steps: use a page.
- For navigation: use the Sidebar.
Anatomy
Open the sheet above to see its parts: the scrim over the page; the panel at the end edge with a header (title and description), the content, and a footer pinned to the bottom with actions (primary first, since footer buttons stack); and the close button in the top end corner.
Variants
| Side | Use for |
|---|---|
End (side={dir === "rtl" ? "left" : "right"}) | The default for panels: filters, quick edit, details of a row |
| Start | Rarely. It sits where the sidebar is, so it reads as navigation |
top | Almost never. Notices belong in an Alert |
bottom | Prefer a Drawer, which adds swipe to close and a handle |
The side prop is physical: "right" is on the right in both directions. Panels belong at the end of the reading direction, so pass the side from the direction, as both examples do. Width is w-3/4, up to max-w-sm from the sm breakpoint.
States
| State | What changes | Token |
|---|---|---|
| Opening | Slides in from its edge | duration-(--duration-slow), ease-standard |
| Closing | Slides out, faster | duration-(--duration-base) |
| Open | Scrim over the page, panel on the modal layer | bg-foreground, opacity-(--opacity-overlay), z-(--z-modal) |
| Focus | Moves to the first focusable element in the panel; returns to the trigger on close | ring-ring |
The scrim color is a default: there is no overlay color token (QUESTIONS Q6). Animation is instant with reduced motion.
Usage guidelines
- Title names the task or the object: "Filter clients", "Edit Omar Haddad".
- A filter sheet's primary action says what it does: "Show 18 clients", not "Apply".
- Keep the panel to one column. If it needs tabs or several sections, it is probably a page.
- Don't open a sheet from a sheet.
Content guidelines
- Title: a verb and an object, or the object's name. Sentence case, no punctuation.
- Description: one sentence on scope or effect: "Changes show on his profile right away."
- Buttons: a specific verb ("Save changes", "Show 18 clients") and "Cancel". Arabic uses the verbal noun: "حفظ التغييرات".
Accessibility
| Key | Does |
|---|---|
| Enter or Space on the trigger | Opens the sheet |
| Tab / Shift+Tab | Cycles through the sheet only; focus is trapped |
| Escape | Closes the sheet and returns focus to the trigger |
- Rendered as
role="dialog"witharia-modal, labelled by the title and described by the description. A screen reader announces "Filter clients, dialog, Show clients that match every filter." - Clicking the scrim closes it, like Escape.
- The close button's hidden label comes from the UI labels ("Close", "إغلاق" in Arabic screens).
- Content behind the sheet is hidden from assistive technology while it's open. Always include a
SheetTitle, even if visually hidden.
RTL
- Pass the side from the direction:
side={dir === "rtl" ? "left" : "right"}. ReaddirwithDirection.useDirection()fromradix-ui. The slide animation and the border follow the side you pass. - The close button sits at the top end corner, inside the panel, in both directions.
- Header text and form fields align to the start.
Tokens used
Read from components/ui/sheet.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --background | bg-background | white | white |
| --foreground | bg-foreground text-foreground | neutral-950 | neutral-950 |
| --muted-foreground | text-muted-foreground | neutral-600 | neutral-600 |
| --ring | ring-ring | brand-600 | orange-500 |
| --secondary | bg-secondary | lime-300 | neutral-100 |
- Layer
z-(--z-modal) - Layer
z-(--z-overlay) - Motion
duration-(--duration-base) - Motion
duration-(--duration-slow) - Motion
ease-standard - Opacity
opacity-(--opacity-overlay) - Radius
rounded-sm - Shadow
shadow-lg - Type
font-semibold - Type
text-sm
Props
<Sheet>
Also takes every prop of Radix Sheet.Root
No props of its own.
<SheetTrigger>
Also takes every prop of Radix Sheet.Trigger
No props of its own.
<SheetClose>
Also takes every prop of Radix Sheet.Close
No props of its own.
<SheetContent>
Also takes every prop of Radix Sheet.Content
| Prop | Type | Default |
|---|---|---|
| side? | "top" | "right" | "bottom" | "left" | "right" |
| showCloseButton? | boolean | true |
<SheetHeader>
Also takes every prop of <div>
No props of its own.
<SheetFooter>
Also takes every prop of <div>
No props of its own.
<SheetTitle>
Also takes every prop of Radix Sheet.Title
No props of its own.
<SheetDescription>
Also takes every prop of Radix Sheet.Description
No props of its own.
Replaces in coach-admin
From the read-only audit of coach-admin: 241 style values, 340 uses.
In coach-admin, side panels are called drawers: the shared Drawer (with lg, xl and 2xl widths), the verification, payout status, bank change, promo code and meal drawers. They become this component. The muscle picker's drawer is part of the Muscle picker (Planned). src/reusableComponents/sheet/ is a spreadsheet grid, not this component.
Classes and selectors
.drawer8.vd-drawer4.drawer-xl4.drawer-lg4.drawer-2xl4.drawer, .drawer-lg, .drawer-xl, .drawer-2xl3.vd-drawer__header2.vd-drawer-shell2.drawer:has(.todo-prefs-panel__body)1.drawer:has(.todo-grouped-card__drawer-list)1.vd-drawer__footer1.vd-drawer__badge1
Files
src/views/Packages/VerificationDrawer/VerificationDrawer.scss142src/reusableComponents/Drawer/Drawer.scss64src/views/Packages/ScPayStatusDrawer/sc-pay-status-drawer.scss41src/views/LandingPage/components/PricingPlansSection/PricingVisibilityDrawer.scss21src/views/Challenge/challenges-list/ChallengePostsDrawer.scss18src/views/Packages/BankChangeDrawer/bank-change-drawer.scss13src/views/Nutrition/Form/BuilderStep/components/MealFormDrawer.scss13src/views/Packages/PromoCodes/promo-code-form-drawer.scss10src/views/Packages/PromoCodes/promo-code-history-drawer.scss10src/views/User/ClientSubscription/ClientSubscriptionDrawer.scss6src/views/Pages/FollowUpTasksPage/Pages/TasksReview/follow-up-request-drawer.scss4src/views/User/components/client-details/tabs-content/audit-log-history-drawer.scss3
The inventory lists the first 12 locations of each value, so file counts are a lower bound.
SCSS variables in these styles
$border-color→--border$success-color→ nearest green-500 (no semantic token)$spacing-mdnot a color: spacing, size or layout$light-success-colornot a color: spacing, size or layout$primary-border-colornot a color: spacing, size or layout$input-border-colornot a color: spacing, size or layout$vd-step-circle-sizenot a color: spacing, size or layout$warning-color→ nearest amber-600 (no semantic token)$warning-text-color→--warning-text$danger-color→--destructive$success-text-color→--success-text$card-surface-bgnot a color: spacing, size or layout
Related
Dialog, Drawer, Alert dialog, RTL.