Skip to content

Snap Bottom SheetA bottom sheet that stops where you asked it to

Snap points you define down to the pixel, one touch that drags the sheet and then scrolls its content, and dialog behaviour out of the box. A framework-free core with a thin React layer, and no runtime dependencies.

Snap Bottom Sheet

Show me code ​

The React bindings render the DOM and hand it to the engine. If you prefer to write the markup yourself, the core does the same job.

tsx
import { useState } from "react";
import { Sheet } from "snap-bottom-sheet/react";

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, 1]}
        defaultSnapIndex={1}
      >
        <Sheet.Portal>
          <Sheet.Overlay className="overlay" />
          <Sheet.Content className="sheet">
            <Sheet.Handle className="handle" />
            <Sheet.Header className="header">
              <Sheet.Title>Ride options</Sheet.Title>
            </Sheet.Header>
            <Sheet.Body className="body">
              <p>Drag the handle, or drag anywhere on the sheet.</p>
            </Sheet.Body>
          </Sheet.Content>
        </Sheet.Portal>
      </Sheet>
    </>
  );
}
ts
import { createSheet } from "snap-bottom-sheet";

const sheet = createSheet(
  {
    content: document.querySelector<HTMLElement>("#sheet")!,
    header: document.querySelector<HTMLElement>("#sheet-header"),
    body: document.querySelector<HTMLElement>("#sheet-body"),
    overlay: document.querySelector<HTMLElement>("#sheet-overlay"),
    handle: document.querySelector<HTMLElement>("#sheet-handle"),
  },
  {
    snapPoints: ["header", 0.5, 1],
    defaultSnapIndex: 1,
    onOpenChange: (open) => {
      if (!open) document.body.classList.remove("sheet-open");
    },
  },
);

document.querySelector("#choose")?.addEventListener("click", () => {
  void sheet.open();
});

The sheet writes only the transform and positioning it has to own. Everything you see above is yours to style: the background, the radius, the shadow and the handle pill.

Where next ​

Released under the MIT License.