Skip to content

Sheet

A modal panel that slides in from an edge of the viewport, for filters, details and secondary tasks that keep the page in context.

Preview

Installation

pnpm add @syntara/react @syntara/tokens
import { Sheet } from '@syntara/react';

Usage

import { Button, DialogTrigger, Sheet } from '@syntara/react';

export function Filters() {
  return (
    <DialogTrigger>
      <Button variant="outline">Filters</Button>
      <Sheet side="end" title="Filters" footer={({ close }) => <Button onPress={close}>Show results</Button>}>
        {/* fields */}
      </Sheet>
    </DialogTrigger>
  );
}

Examples

Sides

start, end, top and bottom. start and end are logical and swap in right-to-left layouts.

Accessibility

KeysAction
EnterorSpaceOn the trigger: opens the sheet and moves focus into it.
TaborShiftTabMoves between focusable elements; focus is trapped inside the sheet.
EscapeCloses the sheet and returns focus to the trigger.
  • A modal dialog (role="dialog") named by its title; the page behind is inert while it is open.
  • Overlays render in a portal on <body>, outside any ThemeScope. When it opens, the overlay looks up data-syntara-theme, data-syntara-scheme, data-syntara-density, dir and lang — each on the nearest ancestor that has it — starting from its trigger (or from where the component is rendered, when controlled), and copies them onto its own root — so it uses the same tenant tokens and direction as the page region it came from, and nested overlays inherit from their parent overlay.
  • Right-to-left: give the ThemeScope a locale (e.g. locale="ar-AE"), which sets dir and wraps React Aria's I18nProvider, so arrow keys and start/end placement flip as well as the layout.
  • The backdrop blurs the page lightly and dims it with backdrop-filter: brightness(0.6), so it recedes in light and dark schemes alike. The panel is glass (--syntara-glass-bg + backdrop blur); only text.default and text.subtle sit directly on it, the two roles the theme engine solves to 4.5:1 over any backdrop.
  • Motion: the panel slides in from its side (start/end follow the reading direction) and leaves faster than it came. With prefers-reduced-motion it appears without moving.

Guidelines

Do

  • Use end for detail and filter panels, bottom for short mobile actions.
  • Keep the primary action in the footer so it stays visible while the body scrolls.

Don’t

  • Don't use a sheet for navigation that should stay visible — use a persistent sidebar.
  • Don't stack sheets.

API reference

Sheet

side
  • start
  • enddefault
  • top
  • bottom

Edge the panel is attached to. end is the right edge in LTR and the left edge in RTL.

titleRequired
ReactNode

Heading; also the panel's accessible name.

description
ReactNode

Supporting text under the title.

children
ReactNode | (({ close }) => ReactNode)

Panel content; scrolls independently of the header and footer.

footer
ReactNode | (({ close }) => ReactNode)

Actions pinned to the bottom of the panel.

isDismissable
boolean

Whether clicking the backdrop closes the sheet.

Default true

isKeyboardDismissDisabled
boolean

Whether Escape is ignored.

Default false

isOpen / defaultOpen / onOpenChange
boolean / boolean / (isOpen: boolean) => void

Open state when not inside a DialogTrigger.

className / style
string / CSSProperties

Applied to the panel, e.g. to change its width.

Tokens

The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.

Colour4
text.defaultsurface.raisedtext.subtleborder.subtle
Type9
font.bodyfont.size.mdline-height.normalfont.headingfont.size.lgfont.weight.semiboldline-height.snugfont.heading-trackingfont.size.sm
Space and size7
space.2space.16space.4card-insetspace.1control-heightfield-gap
Shape1
radius.container
Depth4
glass.blurshadow.overlayglass.opacityhairline
Motion4
motion.duration.normalmotion.easing-outmotion.easingmotion.duration.slow
Other2
sheenrim