CSS
.sheet { background: white; border-radius: 16px 16px 0 0; box-shadow: 0 -8px 40px rgb(0 0 0 / 0.16);}scrollsheet ships mechanics only (position, transform, scroll-snap, backdrop, focus
containment) in scrollsheet/styles.css; import it once next to your components, the same
convention vaul uses. The className on Sheet.Content lands on the panel, so style it like
any card.
Open it and step through menu, key, and remove. One className, no default sheet look.
Zero-config entry
Section titled “Zero-config entry”No bundler CSS handling, Shadow DOM, or a strict CSP? Import from scrollsheet/auto instead:
the same components with the stylesheet embedded, injected the first time a sheet opens. Sheets
only render after JS runs (a native dialog is display:none until opened), so runtime
injection cannot flash unstyled content.
import { Sheet } from "scrollsheet/auto";The embed costs 21.2 kB gzip for Sheet against the default
entry’s 16.9. Toasts on the default entry need both files:
scrollsheet/styles.css and scrollsheet/toast.css. Never mix the two entries in one app;
you would bundle every component twice.
Every number above is measured on a production build. Dev-only warning strings sit behind a development/production export condition, the same one Vite, webpack 5+, and Next.js all set automatically, so they never reach a production bundle. A bundler that doesn’t set that condition just serves the production build in dev too: no warnings, nothing broken.
themeColorDimming goes further: its controller loads from a separate, lazily-fetched chunk, so
an app that never sets the prop never ships it.
Dark mode
Section titled “Dark mode”Light is the default palette everywhere. An app with no theming of its own never flips dark just
because the visitor’s OS is set to dark: the panel, handle, and close button all keep their
light-mode colors regardless of prefers-color-scheme.
Opt into dark by setting data-scrollsheet-theme="dark" on an ancestor, typically <html>:
<html data-scrollsheet-theme="dark">To make the panel track the OS scheme automatically, use "system" instead:
<html data-scrollsheet-theme="system">Any other value, or no attribute at all, renders light. The attribute is page-level: any
ancestor carrying it switches the built-in defaults, and a nested
data-scrollsheet-theme="light" does not switch a subtree back. To theme one sheet differently
from the rest of the page, set the tokens on that sheet instead.
Your own --scrollsheet-bg, --scrollsheet-fg, --scrollsheet-handle,
--scrollsheet-handle-hover, --scrollsheet-close-bg, --scrollsheet-close-color,
--scrollsheet-close-bg-hover, and --scrollsheet-close-color-hover overrides win in every
mode: the attribute only changes which built-in fallback color those variables fall back to.
Toast styling
Section titled “Toast styling”Toaster renders each card as .scrollsheet-toast (icon: .scrollsheet-toast-icon, body:
.scrollsheet-toast-body, title: .scrollsheet-toast-title, description:
.scrollsheet-toast-description, actions: .scrollsheet-toast-action /
.scrollsheet-toast-cancel / .scrollsheet-toast-close) and reads its layout knobs from
--scrollsheet-toast-* custom properties: --scrollsheet-toast-gap,
--scrollsheet-toast-offset, --scrollsheet-toast-mobile-offset. Override either the same way
you’d style any card:
.scrollsheet-toast { border-radius: 12px;}Sonner compatibility
Section titled “Sonner compatibility”Arrived with an existing Sonner integration? Its import { toast, Toaster } from 'scrollsheet'
compat layer also stamps the matching sonner-* class and data-sonner-* attribute on every
element, so your existing .sonner-toast {} CSS keeps matching unmodified. See
Migrate from Sonner. Starting fresh, with no Sonner CSS to keep
matching? <Toaster sonnerCompat={false}> drops the mirrored names and renders the neutral
.scrollsheet-* classes and attributes only.