# @surdeddd/bottom-sheet · Svelte 5
Svelte 5 adapter. Ships `createBottomSheet()` — a rune-friendly controller
factory you wire up via `$effect` and `bind:this`. The helper itself is plain
TypeScript so it works under any Svelte 5 toolchain (Vite, SvelteKit, Astro)
without extra build configuration.
## Install
```bash
npm i @surdeddd/bottom-sheet svelte
```
## Usage
```svelte
Search
Active: {activeId}
```
## Controller API
```ts
type SvelteBottomSheetController = {
attach(refs: {
element: HTMLElement;
handle?: HTMLElement;
scrollContainer?: HTMLElement;
backdrop?: HTMLElement;
screenComponent?: HTMLElement;
}): () => void; // returns teardown
state(): EngineState; // snapshot
on(event, fn): () => void; // unsubscribe
snapTo(id: string): Promise;
open(id?: string): Promise;
close(): Promise;
setAllowed(ids: string[], snap?: string): void;
destroy(): void;
};
```
## SvelteKit / SSR
`createBottomSheet()` does not touch the DOM. The engine attaches only when
you call `ctrl.attach()` from inside `$effect` (which never runs on the
server). SSR pages render the static HTML shell; gestures activate on the
client after hydration.
## One-shot construction
`createBottomSheet(opts)` snapshots `opts` at construction time. Reactive
changes to `opts.snapPoints` / `opts.allowed` / `opts.mode` from the calling
component DO NOT propagate to the engine — by design, to avoid silent
reactivity bugs when callers pass non-reactive object literals.
For runtime updates, use the controller methods:
```svelte
```
Engine recreation (changing `mode`, `animation`, `focusTrap`) requires
calling `ctrl.destroy()` and constructing a fresh controller — the
controller's `attach()` re-uses the constructor-time `opts` snapshot.
## Why a controller, not a `` component?
Distributing a `.svelte` component would require shipping precompiled Svelte
artifacts and a separate build pipeline. The controller pattern keeps the
adapter framework-version-agnostic — it works under Svelte 5 today and stays
forward-compatible as Svelte evolves runes API.