Skip to content
SuperCoach

Components · Overlays

Sheet

Beta

A side panel for secondary tasks, such as filters.

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

SideUse for
End (side={dir === "rtl" ? "left" : "right"})The default for panels: filters, quick edit, details of a row
StartRarely. It sits where the sidebar is, so it reads as navigation
topAlmost never. Notices belong in an Alert
bottomPrefer 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

StateWhat changesToken
OpeningSlides in from its edgeduration-(--duration-slow), ease-standard
ClosingSlides out, fasterduration-(--duration-base)
OpenScrim over the page, panel on the modal layerbg-foreground, opacity-(--opacity-overlay), z-(--z-modal)
FocusMoves to the first focusable element in the panel; returns to the trigger on closering-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.
Client listPanel at the end
Do.The panel opens at the end edge: right in English, left in Arabic.
لوحة في البدايةقائمة العملاء
Don't.side="right" hard-coded. In Arabic the panel opens at the start, over the sidebar.
Do.The primary action says what will happen, and comes first in the stacked footer.
Don't.A vague verb, with a reset button where the coach expects the primary action.

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

KeyDoes
Enter or Space on the triggerOpens the sheet
Tab / Shift+TabCycles through the sheet only; focus is trapped
EscapeCloses the sheet and returns focus to the trigger
  • Rendered as role="dialog" with aria-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"}. Read dir with Direction.useDirection() from radix-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.

Semantic color tokens used by this component
TokenUtilitiesSuperCoachCoach
--backgroundbg-backgroundwhitewhite
--foregroundbg-foreground text-foregroundneutral-950neutral-950
--muted-foregroundtext-muted-foregroundneutral-600neutral-600
--ringring-ringbrand-600orange-500
--secondarybg-secondarylime-300neutral-100
  • Layerz-(--z-modal)
  • Layerz-(--z-overlay)
  • Motionduration-(--duration-base)
  • Motionduration-(--duration-slow)
  • Motionease-standard
  • Opacityopacity-(--opacity-overlay)
  • Radiusrounded-sm
  • Shadowshadow-lg
  • Typefont-semibold
  • Typetext-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

SheetContent props
PropTypeDefault
side?"top" | "right" | "bottom" | "left""right"
showCloseButton?booleantrue

<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.scss142
  • src/reusableComponents/Drawer/Drawer.scss64
  • src/views/Packages/ScPayStatusDrawer/sc-pay-status-drawer.scss41
  • src/views/LandingPage/components/PricingPlansSection/PricingVisibilityDrawer.scss21
  • src/views/Challenge/challenges-list/ChallengePostsDrawer.scss18
  • src/views/Packages/BankChangeDrawer/bank-change-drawer.scss13
  • src/views/Nutrition/Form/BuilderStep/components/MealFormDrawer.scss13
  • src/views/Packages/PromoCodes/promo-code-form-drawer.scss10
  • src/views/Packages/PromoCodes/promo-code-history-drawer.scss10
  • src/views/User/ClientSubscription/ClientSubscriptionDrawer.scss6
  • src/views/Pages/FollowUpTasksPage/Pages/TasksReview/follow-up-request-drawer.scss4
  • src/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

Dialog, Drawer, Alert dialog, RTL.