Getting Started
Install the package, render the parts, and hand them to the engine. This works the same way in React and in plain JavaScript.
Two entry points
| Import | What it is |
|---|---|
snap-bottom-sheet | The core, which needs no framework. It gives you createSheet, steps, and the types. It has no dependencies and no peers. |
snap-bottom-sheet/react | The React bindings. They give you Sheet with its parts, and useSheetState. They are built on the same core. |
There is no default export and no deeper subpath than these two.
1. Install
npm install snap-bottom-sheetpnpm add snap-bottom-sheetyarn add snap-bottom-sheetreact and react-dom (^18 || ^19) are declared as optional peer dependencies, so your package manager will not ask you to install them. The core entry has no peers at all. A vanilla project installs nothing else. The package is ESM only.
2. A sheet in React
The controller writes everything that depends on state. On the React side you only write the part tree and your own state.
import { useState } from "react";
import { Sheet } from "snap-bottom-sheet/react";
import "./sheet.css";
export function RideOptions() {
const [open, setOpen] = useState(false);
return (
<>
<button type="button" onClick={() => setOpen(true)}>
Choose a ride
</button>
<Sheet
open={open}
onOpenChange={setOpen}
snapPoints={["header", 0.5, 0.95]}
defaultSnapIndex={1}
>
<Sheet.Portal>
<Sheet.Overlay className="sheet-overlay" />
<Sheet.Content className="sheet">
<Sheet.Handle className="sheet-handle" />
<Sheet.Header className="sheet-header">
<Sheet.Title>Ride options</Sheet.Title>
<Sheet.Description>Drag the sheet to see more.</Sheet.Description>
</Sheet.Header>
<Sheet.Body className="sheet-body">
<p>Standard · 4 min away</p>
<p>XL · 7 min away</p>
<Sheet.Close>Cancel</Sheet.Close>
</Sheet.Body>
</Sheet.Content>
</Sheet.Portal>
</Sheet>
</>
);
}The package ships no stylesheet, so the sheet stays invisible until you add CSS. Here is the minimum that makes it look like a sheet:
/* sheet.css */
/* The controller positions the overlay itself — colour is yours. */
.sheet-overlay {
background: rgb(0 0 0 / 0.4);
}
.sheet {
/* the library owns position, height and transform — don't set those */
background: white;
border-radius: 16px 16px 0 0;
box-shadow: 0 -4px 24px rgb(0 0 0 / 0.15);
}
.sheet-handle {
align-self: center;
width: 40px;
height: 4px;
margin: 8px 0;
border: 0;
border-radius: 999px;
background: rgb(0 0 0 / 0.2);
}
.sheet-header {
padding: 8px 20px 16px;
}
.sheet-body {
padding: 0 20px 20px;
}TIP
The controller positions the overlay for you. When it attaches, it writes position: fixed and inset: 0 on the overlay. It writes absolute instead of fixed when Sheet.Portal has a container. Your own rule only needs to set the colour.
The overlay fades on its own too. The controller writes --snap-sheet-progress onto the overlay element, from 0 when closed to 1 at the topmost snap, and the default opacity reads that variable. See Styling for every hook.
3. The same sheet in vanilla JS
createSheet attaches the engine to elements you already rendered. Write the markup yourself, then hand the nodes over.
<div id="overlay" class="sheet-overlay"></div>
<div id="sheet" class="sheet">
<div data-snap-sheet-inner>
<button id="handle" class="sheet-handle" type="button"></button>
<div id="header" class="sheet-header"><h2 id="title">Ride options</h2></div>
<div id="body" class="sheet-body"><p>Standard · 4 min away</p></div>
</div>
</div>
<button id="choose" type="button">Choose a ride</button>import { createSheet } from "snap-bottom-sheet";
const el = (id: string) => document.getElementById(id) as HTMLElement;
const sheet = createSheet(
{
content: el("sheet"),
header: el("header"),
body: el("body"),
overlay: el("overlay"),
handle: el("handle"),
},
{
snapPoints: ["header", 0.5, 0.95],
defaultSnapIndex: 1,
labelledBy: "title",
},
);
el("choose").addEventListener("click", () => void sheet.open());The controller starts closed, so nothing is visible until you call open(). The data-snap-sheet-inner wrapper is the element that "content" measures. The full contract is in Vanilla JS.
Next steps
- Core Concepts — the y-offset model, snap indices, content mode, modal versus non-modal.
- Snap Points — every value form,
steps(), and the per-snapscrollanddragoptions. - React API — the full prop surface for
Sheetand every part.