BottomSheet
A snap-point bottom sheet with three resting heights — peek (always visible), half, and full. The user drags the sheet vertically; a velocity-aware flick advances or retreats one snap, otherwise it springs to the nearest level. A backdrop scrim darkens as the sheet rises, and half/full content crossfades in at its level.
Import
import { BottomSheet } from "@roger-emerson/ploxum-primitives";Usage
The sheet is absolutely positioned against the nearest positioned ancestor, so wrap it in a bounded relative container.
<div className="relative h-[480px] overflow-hidden">
<BottomSheet
full={<RouteDetail />}
half={<DriverSummary />}
peek={<p>Trip summary · 24 min away</p>}
/>
</div>Controlled
Pass snap plus onSnapChange to drive the level externally; omit them (optionally with defaultSnap) for uncontrolled behavior.
const [snap, setSnap] = useState<0 | 1 | 2>(0);
<BottomSheet onSnapChange={setSnap} peek={peek} half={half} full={full} snap={snap} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
peek | ReactNode | — | Content always visible, even when collapsed. Required. |
half | ReactNode | — | Content that crossfades in at the half snap (level 1) and above. |
full | ReactNode | — | Content that crossfades in at the full snap (level 2). |
snap | 0 | 1 | 2 | — | Controlled snap level. Pair with onSnapChange. |
defaultSnap | 0 | 1 | 2 | 0 | Initial resting level when uncontrolled. |
onSnapChange | (snap: 0 | 1 | 2) => void | — | Fired whenever the resting snap level changes. |
height | number | 400 | Total sheet height (px) at the full snap. |
halfHeight | number | 240 | Sheet height (px) at the half snap. |
peekHeight | number | 96 | Height (px) of the always-visible peek strip. |
className | string | — | Merged onto the sheet panel. |
ref | Ref<HTMLDivElement> | — | Forwarded to the sheet panel. |
Design rules
- Three snaps only:
0peek,1half,2full. A flick faster than 300px/s advances or retreats one level; a slow release snaps to the nearest offset. - The scrim is transparent at peek and dims progressively (0 → 0.15 → 0.35). It only intercepts pointer events above peek, and clicking it collapses straight back to peek.
- Keyboard: the panel is focusable (
role="dialog"); ArrowUp expands one snap, ArrowDown collapses one snap. - Reduced motion (
prefers-reduced-motion): springs and crossfades become instant (duration 0). Dragging is preserved, but the settle is snapped, not animated. - Spring physics use the ploxum
springs.snappytoken; the grab handle and scrim are decorative (aria-hidden).
