Sheet

An edge-docked modal panel - a side drawer by default, or a bottom sheet with side="bottom" - with drag-to-dismiss.

Sheet playground

Drag the panel toward its edge to dismiss it - the scrim fades with the travel.

Install Sheet

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { Sheet } from '@zyncat/ui/sheet';

Usage

tsx
import { Sheet } from '@zyncat/ui/sheet';
 
<Sheet side="right" open={open} onOpenChange={setOpen}>
<div>Panel content</div>
</Sheet>

Compose Sheet in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.

Sheet props

Sheet

PropTypeDefaultDescription
openboolean—Controlled open state. Omit to stay uncontrolled.
defaultOpenboolean—Initial state when uncontrolled. Default false.
onOpenChange(open: boolean) => void—Fires whenever the open state changes. Pair with `open` for controlled use.
triggerReactElement | null—Cloned to open the sheet.
activateOnActivateOn'click'Whether the trigger fires on `pointerdown` (snappier) or waits for `click`.
side'right' | 'bottom'—Edge the sheet slides in from. Default 'right'.
dismissibleboolean—Scrim/Esc dismissal + drag-to-edge. Default true.
containerHTMLElement | null—Dock inside this element instead of the viewport - it must be positioned (`position: relative` or similar). Scrim, scroll lock and inert scope to it; the rest of the page stays interactive.
idstring—Base id for the panel; drives the trigger's `aria-controls`. Auto-generated when omitted.
...htmlAttributesHTMLAttributes<HTMLDivElement> & DataAttributes—Standard attributes (className, style, data-*, ...) forwarded to the sheet panel.
animationDisableableAnimationopen 'slow'/'entrance', close 'base'/'exit'Open/close timing - motion tokens only, or `null` to disable.
children*ReactNode—The ENTIRE surface - paint AND semantics. Drive dismissal with `open`/`onOpenChange`.

Frequently asked questions

Yes, when you set side="bottom" - the panel docks flush to the bottom edge at full width with drag-down-to-dismiss. It's a prop you set rather than an automatic viewport switch: side defaults to "right", and picking "bottom" on narrow viewports is a decision your own code makes, not something Sheet does on its own.

Grab anywhere on the panel and drag toward its docked edge - past 40% of the panel's own span, or faster than 500px/s, releases it closed; short of that it springs back. Dragging inside a scrolled list doesn't fight the scroll - the drag only takes over once that content is back at its own top edge.

It scroll-locks the page, or the container prop's own box if you pass one - overflow is hidden, the scrollbar gutter is compensated so nothing shifts width, and every sibling outside the sheet gets the inert attribute so it can't be tabbed into or clicked. Closing restores all of it and returns focus to whatever opened the sheet.

No - the trigger only opens it; unlike Popover's trigger, which toggles, Sheet's click handler always sets it open. Close it via the scrim, Escape, the drag gesture, or your own control inside, driving open and onOpenChange if you need it controlled.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub