# DelightStack > Svelte 5 components and an edge-native Cloudflare backend — use either, or both. ### Two products, one design language - [Components →](/getting-started/introduction/) — 55 accessible, delightful Svelte 5 components — actions, display, feedback, forms, media, and navigation. Drop them into any Svelte 5 / Vite app. - [Backend Packages →](/packages/overview/) — Edge-native backend for Cloudflare Workers — auth, reactive database, realtime, AI, billing, and image processing, built on Durable Objects. ### Components **Actions** Buttons, modals, popovers, context menus, command palettes, and theme toggles. **Display** Avatars, tables, calendars, charts, timelines, trees, accordions, and more. **Feedback** Toast notifications, progress indicators, callouts, and confetti celebrations. **Form** Inputs, selects, checkboxes, toggles, file uploads, ratings, and form validation. **Media** Image galleries, carousels, video players, maps, 360° panoramas, and PDF viewers. **Navigation** Tabs, drawers, menus, breadcrumbs, pagination, steps, and bottom sheets. ### Backend Packages - [Auth](/packages/auth/) — JWT sessions, Argon2id, OAuth, magic links, multi-org, and SvelteKit guards. - [Database](/packages/database/) — Reactive SQLite on Durable Objects with full-text + vector search and live sync. - [Realtime](/packages/websocket/) — Room-scoped WebSockets with reactive presence, typed events, and DB sync. - [AI](/packages/ai/) — Chat completions, resumable streaming, and vector embeddings via AI Gateway. - [Billing](/packages/stripe/) — Stripe subscriptions, metered usage, webhooks, and subscription guards. - [Images](/packages/images/) — Variants, ThumbHash, EXIF, and face-aware crops via Cloudflare Containers. - [Rate Limiter](/packages/rate-limiter/) — Token-bucket rate limiting as a Durable Object, with zero runtime dependencies. --- # Architecture > The Cloudflare Workers + Durable Objects topology the backend packages run on — DO classes, bindings, migrations, and the two-worker model. Every [backend package](/packages/overview/)'s server is a **Durable Object** (DO). To use one, you re-export its DO class from a Worker, declare it in `wrangler.toml` (with a migration), and bind it where you need it. **Note:** This page only applies to the **full-stack path**. The component library needs none of this — it's a plain npm dependency. ## The two-worker model The reference setup splits into two Workers: ``` ┌─────────────────────────────┐ ┌──────────────────────────────────────┐ │ SvelteKit app worker │ │ Backend worker │ │ (your routes + UI) │ │ hosts the Durable Object classes: │ │ │ cross │ • AuthDatabaseServer (auth) │ │ binds the DO classes via │ ─────▶ │ • OrgDatabaseServer (database) │ │ `script_name` (cross- │ script │ • AppWebsocketServer (websocket) │ │ script Durable Objects) │ refs │ • RateLimiterServer (rate-limit)│ │ │ │ • ImageProcessorContainer (images) │ └─────────────────────────────┘ └──────────────────────────────────────┘ ``` The **backend worker owns the DO classes + migrations**; the app worker references them with `script_name`. You can also run everything in a **single worker** — just declare the classes and migrations in that one worker instead. **Caution:** With the two-worker model, **deploy the backend worker first**. The app worker's DO bindings reference classes that must already exist. ## 1. Re-export the Durable Object classes In your backend Worker entry (`src/index.ts`), re-export every DO class you use: ```typescript // Stateless DOs can be re-exported directly: export { RateLimiterServer } from '@delightstack/rate-limiter'; export { ImageProcessorContainer } from '@delightstack/images/worker'; // The others are subclassed to inject config (secrets, schema, hooks): import { AuthDatabaseServer as BaseAuth } from '@delightstack/auth/worker'; import { DatabaseServer } from '@delightstack/database/worker'; import { WebsocketServer } from '@delightstack/websocket/worker'; export class AuthDatabaseServer extends BaseAuth { /* inject auth config — see the Auth guide */ } export class OrgDatabaseServer extends DatabaseServer { /* inject your schema — see the Database guide */ } export class AppWebsocketServer extends WebsocketServer { /* inject hooks — see the Realtime guide */ } ``` **Note:** Stateless DOs (`RateLimiterServer`, `ImageProcessorContainer`) can be re-exported as-is. The auth, database, and websocket servers are subclassed so you can inject config (secrets, schema, hooks) — the class name you export is what goes in `wrangler.toml`. ## 2. Declare classes, migrations, and bindings Durable Objects backed by SQLite must be registered with a migration the first time they're added: ```toml # wrangler.toml (backend worker) name = "my-app-server" main = "src/index.ts" [[migrations]] tag = "v1" new_sqlite_classes = [ "AuthDatabaseServer", "OrgDatabaseServer", "AppWebsocketServer", "RateLimiterServer", "ImageProcessorContainer", ] [[durable_objects.bindings]] name = "AUTH" class_name = "AuthDatabaseServer" [[durable_objects.bindings]] name = "DB" class_name = "OrgDatabaseServer" # …WS, RATE_LIMITER, IMAGE_PROCESSOR follow the same pattern [ai] binding = "AI" # required by @delightstack/ai [[kv_namespaces]] binding = "KV" id = "" [[r2_buckets]] binding = "R2" # image originals + variants bucket_name = "" ``` ## 3. Bind the DOs into your app worker If your SvelteKit app is a separate Worker, bind the classes **cross-script** with `script_name`: ```jsonc // wrangler.jsonc (app worker) "durable_objects": { "bindings": [ { "name": "AUTH", "class_name": "AuthDatabaseServer", "script_name": "my-app-server" }, { "name": "DB", "class_name": "OrgDatabaseServer", "script_name": "my-app-server" }, { "name": "WS", "class_name": "AppWebsocketServer", "script_name": "my-app-server" }, { "name": "RATE_LIMITER", "class_name": "RateLimiterServer", "script_name": "my-app-server" } ] } ``` Your SvelteKit server code then reaches them through `platform.env`: ```typescript const db = platform.env.DB.get(platform.env.DB.idFromName(org_id)); const ws = platform.env.WS.get(platform.env.WS.idFromName(org_id)); ``` ## Naming Durable Object instances DO classes are types; instances are addressed by ID. The convention across the packages is one instance **per tenant** (org / room / user): `idFromName(org_id)` for the database and websocket, `idFromName(ip_or_user)` for the rate limiter. Cloudflare guarantees a single instance per ID, which is what gives you the single-writer model. ## Compatibility - `compatibility_date` recent enough for SQLite-backed DOs and `nodejs_compat`. - `compatibility_flags = ["nodejs_compat"]` on the app worker (the Stripe SDK and others use node builtins). ## Next steps - [Quick Start: Full Stack](/getting-started/quick-start-stack/) — run the example app, which wires all of this together - [Packages overview](/packages/overview/) — pick a package for its full configuration --- # Installation > How to install and configure @delightstack/components in your Svelte 5 project. This page covers installing the **component library** into a Svelte 5 app. To set up the Cloudflare backend packages instead, start with [Quick Start: Full Stack](/getting-started/quick-start-stack/). ## Prerequisites DelightStack requires **Svelte 5** (`^5.36.0`). It works with: - **SvelteKit** — the recommended framework for Svelte apps - **Vite + Svelte** — for standalone Svelte projects ## Install the Package 1. **Add the component library to your project:** ```bash pnpm add @delightstack/components ``` **Note:** The `@delightstack/utilities` package is included automatically as a dependency — you do not need to install it separately. 2. **Add the design tokens:** Components read all their colors, typography, spacing, and motion from CSS custom properties shipped by `@delightstack/styles`. Install it and import it once at the root of your app: ```bash pnpm add @delightstack/styles ``` ```ts // e.g. src/routes/+layout.svelte (SvelteKit) or your app entry import '@delightstack/styles'; ``` This defines every token — each with light and dark values built in — plus a small set of base element styles. To re-theme, override the brand seed (which re-derives the action/accent colors and the neutral ramp) or any individual token in your own CSS: ```css :root { /* Re-derives the whole palette from one brand color */ --color-primary: #2563eb; /* …or override individual semantic tokens outright */ --color-action: light-dark(#2563eb, #3b82f6); --radius-md: 0.5rem; } ``` **Tip:** Need the tokens but not the base element styles (e.g. inside an existing design system)? Import `@delightstack/styles/tokens.css` instead. See the [Design Tokens](/guides/design-tokens/) reference for the complete list. 3. **Start using components:** ```svelte alert('It works!')}> Hello, DelightStack ``` ## Optional Peer Dependencies Some components require additional libraries that are not bundled with DelightStack. Install them only if you use the corresponding component: | Component | Peer Dependency | Install Command | | ------------ | ------------------------- | ----------------------------------- | | **Panorama** | `three` | `pnpm add three` | | **PDF** | `pdfjs-dist` | `pnpm add pdfjs-dist` | These are loaded dynamically at runtime, so they will not affect your bundle size unless you actually use the component. ## TypeScript TypeScript types are included automatically — no additional `@types/*` packages or configuration needed. The library exports types for all component props, event details, and data structures: ```typescript import type { SelectOption, TableColumn, TreeNode, GalleryImage, CommandOption, CalendarEvent, } from '@delightstack/components'; ``` ## Next Steps - [Quick Start: Components](/getting-started/quick-start/) — Build a complete form example - [Design Tokens](/guides/design-tokens/) — Full reference of CSS custom properties - [Theming](/guides/theming/) — Customize the look and feel --- # Introduction > What DelightStack is — a polished Svelte 5 component library and an edge-native Cloudflare backend — and how to choose your starting point. DelightStack is two products that share one design language: 1. **The component library** (`@delightstack/components`) — 55 polished, accessible **Svelte 5** components with thoughtful micro-animations and a complete design-token system. It drops into any Svelte 5 app and has no backend requirements. 2. **The backend packages** — auth, reactive database, realtime, AI, billing, image processing, and rate limiting for **Cloudflare Workers**, built on **Durable Objects**. They're designed to work together and with the components, but each stands on its own. You can adopt either half without the other. Most people start with the components; teams building on Cloudflare get a full-stack toolkit. ## Choose your path **Just the components** You have a Svelte 5 app (any backend, or none) and want delightful UI. 1. [Installation](/getting-started/installation/) — add the package and design tokens 2. [Quick Start: Components](/getting-started/quick-start/) — build a form in minutes 3. [Component reference](/components/overview/) — browse all 55 components **The full stack** You're building a full-stack app on Cloudflare Workers and want batteries included. 1. [Quick Start: Full Stack](/getting-started/quick-start-stack/) — run the example app 2. [Architecture](/getting-started/architecture/) — the Durable Object topology 3. [Package reference](/packages/overview/) — auth, database, realtime, and more **Note:** The backend packages are **infrastructure, not drop-in libraries** — they assume Cloudflare Workers with Durable Object bindings and migrations. The component library has no such requirements; it works in any Svelte 5 / Vite project. ## Design philosophy - **Details Matter** — Every pixel, every transition, every interaction is intentional. - **Delight Users** — Subtle animations, smooth transitions, and unexpected moments of joy. - **Opinionated by Default** — Everything looks and works great out of the box with sensible defaults. - **Modern Platform** — Plain CSS with custom properties, `light-dark()`, and container queries on the frontend; SQLite-backed Durable Objects at the edge on the backend. No heavy abstractions. - **One Error, One Convention** — A single `DelightError` class, snake_case data, camelCase callbacks, and Svelte 5 runes throughout. ## The component library **Svelte 5 Native** Built from the ground up with runes (`$props`, `$state`, `$derived`, `$effect`), snippets, and the `{@attach}` directive. No legacy compatibility layers. **55 Components** Everything you need — buttons, modals, forms, tables, charts, maps, video players, and more. **Dark Mode Built-in** Every component supports light and dark mode via CSS `light-dark()`. No JavaScript theme switching required. **Accessible** ARIA attributes, keyboard navigation, focus management, and `prefers-reduced-motion` support baked into every component. **Design System Included** A complete set of CSS custom properties for colors, typography, spacing, shadows, and animations — fully customizable. **Type-Safe** Full TypeScript support with exported types for all component props, events, and data structures. All 55 components — across **Actions**, **Display**, **Feedback**, **Form**, **Media**, and **Navigation** — import from a single entry point: ```svelte ``` Components use **callback props** (`onclick`, `onchange`) instead of dispatched events, support **two-way binding** via `bind:value`, and automatically show loading states when callbacks return Promises. Browse the full catalog in the [component reference](/components/overview/), or see them all working together in the [Dashboard Demo](/demo/). ## The backend packages Each package's server runs inside a **Durable Object** — one instance per tenant, single-writer, with transactional SQLite storage. There's no external database, queue, or cache to provision; everything runs on Cloudflare's network, close to your users. - [Auth](/packages/auth/) — JWT sessions, Argon2id, OAuth, magic links, multi-org, invitations, and SvelteKit route guards. - [Database](/packages/database/) — Type-safe reactive SQLite with full-text + vector search, Zod validation, and automatic migrations. - [Realtime](/packages/websocket/) — Room-scoped WebSockets with reactive presence, typed events, and live database sync. - [AI](/packages/ai/) — Chat completions, resumable streaming, and vector embeddings via Cloudflare AI Gateway. - [Billing](/packages/stripe/) — Stripe subscriptions, metered usage, webhooks, and subscription guards. - [Images](/packages/images/) — Variants, ThumbHash, EXIF, color extraction, and face-aware crops via Cloudflare Containers. - [Rate Limiter](/packages/rate-limiter/) — Token-bucket rate limiting as a Durable Object, with zero runtime dependencies. Most packages ship a **reactive Svelte 5 client** (`$state`/`$derived` with optimistic updates) and a `create*Handle()` that plugs straight into SvelteKit's `handle` hook. The [Architecture](/getting-started/architecture/) page explains how the pieces are wired together. ## Next steps - [Installation](/getting-started/installation/) — add the component library to your project - [Quick Start: Components](/getting-started/quick-start/) — build your first form in minutes - [Quick Start: Full Stack](/getting-started/quick-start-stack/) — run the full example app on Cloudflare - [Architecture](/getting-started/architecture/) — how the backend packages fit together - [Dashboard Demo](/demo/) — see all the components in action in a realistic dashboard --- # Quick Start: Components > Build your first form with DelightStack components in minutes. This guide walks you through building a complete contact form with validation, feedback, and polished interactions — all in a single Svelte component. It assumes you've completed the [Installation](/getting-started/installation/) steps. **Note:** This is the quick start for the **component library** — it works in any Svelte 5 app, with any backend. If you're building a full-stack app on Cloudflare, see [Quick Start: Full Stack](/getting-started/quick-start-stack/). ## Import Syntax All components are available from a single entry point: ```svelte ``` There are no sub-paths or category-specific imports. Unused components are tree-shaken automatically. ## Your First Component Every component works out of the box with sensible defaults: ```svelte ``` ## Bindable Props Form components support two-way binding with `bind:value`, so your state stays in sync automatically: ```svelte

Hello, {name}! Notifications: {notifications ? 'on' : 'off'}

``` ## Callback Props Components use callback props (`onclick`, `onchange`, `onsubmit`) instead of dispatched events. When a callback returns a Promise, the component automatically shows a loading state: ```svelte ``` ## Complete Example: Contact Form Here is a realistic form using `Input`, `Select`, `Toggle`, `Button`, and `Toaster` for feedback: ```svelte
{}}> ``` **Tip:** The `Button` automatically displays a loading spinner while `handleSubmit` runs because the `onclick` callback returns a Promise. No manual loading state management needed. ## Using the Form Component with Validation For built-in validation, wrap your fields in a `Form` component with a Standard Schema validator (Zod, Valibot, or ArkType): ```svelte
``` See [Working with Forms](/guides/forms/) for the full pattern. **Tip:** Pair the client with [Realtime](/packages/websocket/) via `ws.databaseHooks()` so entity changes sync live across tabs and clients. **Note:** Need raw SQL? `SqlServer` (from `@delightstack/database/server`) gives you full control. See the [package README](https://github.com/brianschwabauer/delightstack/tree/main/packages/database) for search tuning, transactions, and sync. --- # Editor > A delightful rich text / block editor for Svelte 5 built on ProseMirror — slash commands, drag & drop blocks, magnetic resize, optimistic uploads, and an extensible block system. `@delightstack/editor` is a rich text / block editor for Svelte 5, built on raw ProseMirror. It ships the writing basics done right (marks, headings, lists, todos, quotes, code, links, dividers), Notion-style block UX (slash commands, floating selection menu, a gutter handle for dragging and block actions, FLIP reorder animations), optimistic media uploads with progress, and a one-object API for registering custom blocks — plus a zero-dependency server renderer for public pages. ## Live demo Every built-in node, mark, and block in one document. Type `/` for commands, select text for the floating menu, hover a block for its drag handle (click it for block actions — turn into, duplicate, delete), drop an image to upload (simulated), resize the image with its side grips, click the callout's gear for settings, and paste markdown or a YouTube URL. Uploads here are served from local blob URLs — a real app wires the same `Uploader` interface to [`images`](/packages/images/). ```svelte ``` ## Features - **Runes-reactive `Editor` class** — `editor.doc`, `selection`, `active_marks`, `active_block`, `can_undo`, `is_empty`, `focused`, and `uploads` all usable directly in templates. - **Menus from one definition** — an `EditorCommand` registered once appears consistently in the `/` slash menu, gutter plus menu, toolbar, and floating selection menu. - **Markdown everywhere** — `# `, `- `, `[] `, `> `, ` ``` `, `**bold**` while typing; plain-text markdown pastes become rich content; Word/Google Docs pastes are scrubbed to clean semantics. - **Optimistic uploads** — placeholder nodes with blob previews and progress; deletion aborts; failures show retryable error states; `getJSON()` never leaks blob URLs. - **Magnetic snap resize** — configurable snap points with gravity easing and hysteresis, labeled ghost badges while dragging. - **Block system** — `defineBlock()` registers schema + Svelte node view + interactive chrome + settings popover + menu entries in one object. - **Server renderer** — `@delightstack/editor/render` has zero dependencies (no svelte, no prosemirror) and runs in Cloudflare Workers. - **Collab-ready seams** — isomorphic schema + `schemaHash`, injectable history, wrappable dispatch, stable `block_id` attrs, and an `EditorTransport` interface for the upcoming prosemirror-collab + Durable Object phase. ## Install ```bash pnpm add @delightstack/editor ``` | Import | Use | | --- | --- | | `@delightstack/editor` | `Editor` class, `defineBlock`, `defaultBlocks`, types | | `@delightstack/editor/components` | ``, ``, menus | | `@delightstack/editor/render` | `renderHTML`, `renderText` — server/Worker-safe | | `@delightstack/editor/schema` | `buildSchema`, `schemaHash` — isomorphic (collab) | `@delightstack/components` is a peer dependency — the editor chrome is built from the design system (`Button`, `Select`, `Input`, `Range`, `Toggle`), and the callout/gallery blocks render the real `` and `` components. Editing a callout in the editor is editing the same component your page renders. ## Basic usage ```svelte ``` `` includes the slash menu, floating menu, and gutter controls by default; disable any of them with `slash_menu={false}` / `floating_menu={false}` / `plus_button={false}`. Pass `readonly` for display mode (same component, chrome hidden) — or better, use `renderHTML` and skip the editor bundle entirely on public pages. ## Uploads Provide an `Uploader` and the image/gallery/video/audio/file blocks light up (drop, paste, and slash-command entry points): ```typescript import { imageURL, toImageProps } from '@delightstack/images'; import type { Uploader } from '@delightstack/editor'; const uploader: Uploader = { async upload(file, { kind, signal, on_progress }) { const record = await myImagesApi.upload(file, { signal, on_progress }); return { image: { id: record.id, width: record.width, height: record.height, src: imageURL(record.id), srcset: toImageProps(record).srcset, thumbhash: record.thumbhash, }, }; }, }; new EditorClass({ blocks: defaultBlocks(), uploader }); ``` The editor inserts a real placeholder node immediately (blob preview + progress ring), so the document stays consistent through edits, moves, and undo while the upload runs. On completion the node's attrs are swapped in place without polluting undo history. ## Custom blocks ```typescript import { defineBlock } from '@delightstack/editor'; import StatBlock from './StatBlock.svelte'; const stat = defineBlock<{ value: number; label: string }>({ name: 'stat', schema: { group: 'block', atom: true, attrs: { value: { default: 0 }, label: { default: '' } }, toDOM: (node) => ['div', { 'data-block': 'stat', 'data-value': node.attrs.value }], parseDOM: [{ tag: 'div[data-block="stat"]' }], }, component: StatBlock, settings: [ { attr: 'value', label: 'Value', control: 'text' }, { attr: 'label', label: 'Label', control: 'text' }, ], commands: [ { name: 'stat', label: 'Stat', group: 'Embeds', run: (e) => e.insertBlock('stat') }, ], render: (node, ctx) => `
${ctx.esc(node.attrs?.value)} ${ctx.esc(node.attrs?.label)}
`, }); ``` The node view component receives `BlockProps`: reactive `attrs`, `selected`, `editable`, plus `update_attrs(patch)` (dispatches a transaction), `delete_node()`, `open_settings()`, and the `content` attachment marking the editable hole for non-atom blocks (`
`). Props in, transactions out — no DOM back-channels. Add `interactive: { resize: { attr: 'width_pct' } }` for magnetic-snap resizing, or `interactive: false` to opt out of all chrome. **Tip:** Planning to use collaborative editing later? Define custom block **schemas** with `defineBlockSchema()` in a file shared with your Worker and compose the UI half client-side — `buildSchema()` and `schemaHash()` from `@delightstack/editor/schema` are worker-safe. ## Server rendering ```typescript import { renderHTML, renderText } from '@delightstack/editor/render'; // +page.server.ts — no editor code ships to the browser export const load = async () => { const doc = await getDocument(); return { html: renderHTML(doc, { image_url: (id) => imageURL(id) }) }; }; // Search indexing / AI context const plaintext = renderText(doc); ``` Custom blocks render through the same `render` function declared in their spec — pass them with `renderHTML(doc, { blocks: { stat: statRenderer } })`. ## Roadmap Designed and planned, landing in phases: collaborative editing (prosemirror-collab with a Durable Object authority over [`websocket`](/packages/websocket/)), live cursors via [`presence`](/packages/presence/), comments anchored through the step log ([`database`](/packages/database/)), version history / time travel, AI writing suggestions via [`ai`](/packages/ai/), `@` mentions, and layout/column blocks. --- # Images > Cloudflare Container–based image processing — optimized variants, ThumbHash placeholders, EXIF, color extraction, and face-aware crops. `@delightstack/images` is image processing for Cloudflare Workers. Upload an image and get back optimized variants, rich metadata, a ThumbHash placeholder, extracted colors, and everything you need to display images beautifully — powered by **Sharp/libvips** running in a **Cloudflare Container**. ## Features - **Optimized variants** — resize and encode to AVIF, WebP, JPEG, or PNG with quality/fit control. - **Rich metadata** — dimensions, EXIF (orientation, GPS, date), color space, ICC profile. - **ThumbHash placeholders** — ~33-char hash → blurred preview with transparency + aspect ratio. - **Color extraction** — background and accent color in OKLCH for native CSS. - **Face-aware crops** — MediaPipe BlazeFace for smart square avatars. - **Special formats** — SVG sanitization, PDF first-page render, animated GIF/WebP/APNG. - **CDN serving** — a SvelteKit hook with ETag/304 and immutable caching. ## How it works The package has two halves: **worker-side code** (runs in your Worker / Database DO) and a **Docker container** (`ImageProcessorContainer`, runs in Cloudflare Containers with native libraries). The worker stores originals/variants in **R2**; the container does the heavy lifting and returns a `multipart/mixed` response of metadata + binary variants. ```bash pnpm add @delightstack/images ``` | Import | Use | | --- | --- | | `@delightstack/images/worker` | `ImageProcessorContainer` — the Container Durable Object | | `@delightstack/images` | `imageProcessing`, `createImageHandle`, `defineImageTable`, `toImageProps` | Needs an `R2` bucket and the `ImageProcessorContainer` declared in `wrangler.toml`, plus the container `Dockerfile` (shipped in the package) — see [Architecture](/getting-started/architecture/). ## Two modes - **Mode 1 — database-backed (async):** `imageProcessing()` stores the upload, schedules a DO alarm, and returns immediately; the alarm processes and updates the record. Best for user uploads. - **Mode 2 — standalone (sync):** call the processor directly and get variants back in the response. ## CDN hook + display ```typescript // hooks.server.ts — serve variants from R2 with caching import { createImageHandle } from '@delightstack/images'; export const imageHandle = createImageHandle({ getBucket: (event) => event.platform.env.R2, }); ``` ```svelte … ``` **Note:** `toImageProps()` feeds the [`Image`](/components/media/image/) component its background color, ThumbHash, and variant URLs for three-tier progressive loading. See the [package README](https://github.com/brianschwabauer/delightstack/tree/main/packages/images) for watermarks, face cropping, and container deployment. --- # Packages Overview > DelightStack's edge-native backend packages for Cloudflare Workers — auth, database, realtime, AI, billing, and images, built on Durable Objects. DelightStack's **backend packages** are for building full-stack apps on **Cloudflare Workers**. Each package is edge-native, built on **Durable Objects**, and designed to work together — but every one of them stands on its own. **Note:** The backend packages are a separate product from the [component library](/getting-started/introduction/). You can use the components without the backend, the backend without the components, or both together. ## The packages - [@delightstack/auth](/packages/auth/) — JWT sessions, Argon2id password hashing, OAuth, magic links, multi-org, invitations, and SvelteKit route guards. - [@delightstack/database](/packages/database/) — Type-safe reactive SQLite on Durable Objects, with full-text + vector search (Orama), Zod validation, and automatic migrations. - [@delightstack/websocket](/packages/websocket/) — Room-scoped realtime with reactive presence, typed events, hibernation, and live database sync. - [@delightstack/presence](/packages/presence/) — Live cursors, online roster, cursor chat, reactions, and field presence — on a swappable transport and identity. - [@delightstack/ai](/packages/ai/) — Chat completions, resumable streaming over WebSocket, and async vector embeddings via Cloudflare AI Gateway. - [@delightstack/stripe](/packages/stripe/) — Stripe billing — products, subscriptions, metered usage, webhooks, and subscription guards. - [@delightstack/images](/packages/images/) — Image processing via Cloudflare Containers — variants, ThumbHash, EXIF, color extraction, and face-aware crops. - [@delightstack/rate-limiter](/packages/rate-limiter/) — Token-bucket rate limiting as a Durable Object, with zero runtime dependencies. ## What they share - **Durable Objects everywhere.** Each package's server lives in a Durable Object, giving you a **single-writer model** — one instance handles all writes for a given key, so there are no race conditions. SQLite provides durable, transactional storage inside the DO. - **Edge-native.** No external database, queue, or cache to provision. Everything runs on Cloudflare's network, close to your users. - **Reactive Svelte 5 clients.** `auth`, `database`, `websocket`, `ai`, and `stripe` ship a client built on Svelte 5 runes (`$state`/`$derived`) with off-thread workers and optimistic updates. - **SvelteKit handles.** Most packages expose a `create*Handle()` that plugs into SvelteKit's `handle` hook, wiring API routes, auth, and CSRF for you. - **`DelightError` for errors.** Every package throws the single `DelightError` class from `@delightstack/utilities` for consistent, serializable error responses. ## Not drop-in libraries — infrastructure Unlike the component library, these packages assume a specific **Cloudflare Workers + Durable Objects topology**: you re-export the DO classes from a Worker, declare them in `wrangler.toml` with migrations, and bind them into your app. The [Architecture](/getting-started/architecture/) page explains that wiring, and the [example app](https://github.com/brianschwabauer/delightstack/tree/main/apps/example-app) is the canonical reference for putting it all together. ## Next steps - [Architecture](/getting-started/architecture/) — Durable Object topology, bindings, and migrations - [Quick Start: Full Stack](/getting-started/quick-start-stack/) — stand up the example app end-to-end - Pick a package from the sidebar to go deep --- # Presence > Real-time presence for Svelte 5 — online roster, live cursors, cursor chat, reactions, and field presence, on a swappable transport and identity. `@delightstack/presence` adds a real-time **presence** layer for Svelte 5 — an online roster, live cursors, cursor chat, reactions, and field/cell presence. It builds on `@delightstack/websocket` and `@delightstack/auth`, but both are **optional**: the core depends only on two small interfaces, so any transport or identity provider can be dropped in. ```svelte
``` **Tip:** The demo above is real — open this page in a second browser tab and your cursor appears in both. The two extra cursors are simulated peers so the demo isn't lonely. ## Features - **Online facepile** — a deduplicated roster of online members (merged across each user's tabs) with status dots, idle dimming, overflow, and hovercards. - **Live cursors** — pointer positions normalized across viewport sizes and spring-smoothed; shown only for peers on the same page. - **Cursor chat** — Figma-style ephemeral messages that ride along your cursor (press `/`). - **Reactions** — fire-and-forget emoji that float up for everyone on the page. - **Field presence** — a colored ring + name badge showing who's editing which field or cell. - **Swappable transport & identity** — the core never imports websocket or auth; the default adapters are optional peers. ## Install ```bash pnpm add @delightstack/presence ``` | Import | Use | | --- | --- | | `@delightstack/presence` | `PresenceClient`, `setPresence`/`getPresence`, `trackCursor`, `fieldPresence` | | `@delightstack/presence/adapters` | `createDelightPresence`, `websocketTransport`, `authIdentity` | | `@delightstack/presence/components` | `PresenceAvatars`, `Cursors`, `Reactions`, `FieldPresence` | | `@delightstack/presence/server` | `createPresenceServer`, `PRESENCE_EPHEMERAL_EVENTS` | ## 1. Add presence to the WebSocket server Compose the presence module into your Durable Object so `presence:*` messages are relayed, new joiners get an instant snapshot, and cursor traffic uses a generous rate bucket. ```typescript import { WebsocketServer } from '@delightstack/websocket/worker'; import { createPresenceServer, PRESENCE_EPHEMERAL_EVENTS } from '@delightstack/presence/server'; export class AppWebsocketServer extends WebsocketServer { constructor(ctx: DurableObjectState, env: Env) { const presence = createPresenceServer(); super( { onMessage: presence.onMessage, onDisconnect: presence.onDisconnect, rate_limit: { ephemeral_events: PRESENCE_EPHEMERAL_EVENTS }, }, ctx, env, ); } } ``` ## 2. Create the client and provide context ```typescript // +layout.ts — alongside your websocket/auth clients import { createDelightPresence } from '@delightstack/presence/adapters'; const presence = createDelightPresence({ ws, auth }); return { auth, ws, presence }; ``` ```svelte {@render children()} ``` ## 3. Drop in the pieces ```svelte
...
``` **Note:** Cursor coordinates are stored relative to the nearest `data-presence-stage` element (or the document root), so they map to the same logical point across different viewport sizes. ## Swappable transport & identity The core depends on two interfaces, so a different stack works by implementing them and constructing `PresenceClient` directly: ```typescript import { PresenceClient } from '@delightstack/presence'; import type { PresenceTransport, PresenceIdentity } from '@delightstack/presence'; const transport: PresenceTransport = { get connected() { return socket.isConnected; }, get sessions() { return socket.members.map((m) => ({ id: m.id })); }, send: (msg) => socket.publish('presence', msg), on: (handler) => socket.subscribe('presence', handler), }; const identity: PresenceIdentity = { get user() { return me ? { id: me.id, name: me.name, image: me.avatar } : null; }, get orgId() { return me?.orgId ?? null; }, }; const presence = new PresenceClient({ transport, identity }); ``` Your transport must relay every `presence:*` message room-wide. With `@delightstack/websocket`, that's exactly what `createPresenceServer` does. ## How it works - **Awareness model.** Each tab owns a small ephemeral state object; changes broadcast with a monotonic clock and merge last-writer-wins. State is never persisted — it clears on disconnect (graceful `presence:remove`, or a TTL backstop). - **Per-tab keys.** A SharedWorker shares one connection across a browser's tabs, so presence is keyed by a per-tab id; cursors filter out your own user by default. - **Joins.** A newcomer sends `presence:request`; the server replies with a snapshot to just that client. - **Rate.** Cursor updates are throttled client-side and use the server's generous ephemeral bucket. **Caution:** Presence is browser-only and SSR-safe (components render nothing on the server). Cursors and reaction layers are `aria-hidden`, and motion respects `prefers-reduced-motion`. --- # Rate Limiter > Token-bucket rate limiting for Cloudflare Workers as a Durable Object, with zero runtime dependencies. `@delightstack/rate-limiter` is a token-bucket rate limiter implemented as a Durable Object. Pick a key (IP, user ID, API key), and the single-instance guarantee handles concurrency for you. Zero runtime dependencies — it only imports `cloudflare:workers`. ## Features - **Token bucket** — a bucket refills at a constant rate up to a max; requests consume tokens and are rejected when empty. - **Per-key buckets** — one DO instance manages many independent buckets (`login`, `api`, `upload`) so you can limit different actions separately. - **Native DO RPC** — `consume()`, `check()`, `getStatus()`, `setOptions()`, `reset()` called directly, no fetch handlers. - **Header-ready status** — `getStatus()` returns `remaining`, `limit`, and `reset_in_ms`. - **In-memory only** — state isn't persisted; limits reset if the DO is evicted (ideal for rate limiting — no storage latency, transient state is fine). ## Install ```bash pnpm add @delightstack/rate-limiter ``` `@delightstack/rate-limiter` exports a single entry: `RateLimiterServer`. ## 1. Re-export and bind the Durable Object ```typescript export { RateLimiterServer } from '@delightstack/rate-limiter'; ``` ```toml # wrangler.toml [[durable_objects.bindings]] name = "LIMITER" class_name = "RateLimiterServer" [[migrations]] tag = "v1" new_sqlite_classes = ["RateLimiterServer"] ``` ## 2. Use it Address one instance **per identity** with `idFromName()` — an IP for per-IP limiting, a user ID for per-user, or a fixed string for a global limit: ```typescript const id = env.LIMITER.idFromName(ip_address); const limiter = env.LIMITER.get(id); // configure the bucket for this key (once) await limiter.setOptions('api', { limit: 100, refill_per_second: 10 }); const allowed = await limiter.consume('api', 1); if (!allowed) { const status = await limiter.getStatus('api'); return new Response('Too Many Requests', { status: 429, headers: { 'X-RateLimit-Limit': String(status.limit), 'X-RateLimit-Remaining': String(status.remaining), 'Retry-After': String(Math.ceil(status.reset_in_ms / 1000)), }, }); } ``` **Note:** The same instance holds separate buckets per `key`, so one limiter can enforce different limits for `login`, `api`, and `upload` at once. See the [package README](https://github.com/brianschwabauer/delightstack/tree/main/packages/rate-limiter). --- # Billing (Stripe) > Stripe billing for Cloudflare Workers and SvelteKit — products, subscriptions, metered usage, webhooks, and subscription guards. `@delightstack/stripe` is Stripe billing for SvelteKit apps on Cloudflare Workers — define your plans and meters in code, sync them to Stripe, handle webhooks, and gate features by subscription. ## Features - **Config-driven plans & meters** — declare them once with `defineBillingConfig`; sync to Stripe. - **Subscriptions** — checkout, subscription state, and a webhook-driven sync into your database. - **Metered usage** — report usage events for usage-based pricing. - **Webhooks** — verified webhook handling with `handleWebhook`. - **Subscription guards** — gate SvelteKit routes by plan/entitlement. ## Install ```bash pnpm add @delightstack/stripe stripe ``` | Import | Use | | --- | --- | | `@delightstack/stripe/server` | `defineBillingConfig`, `createBillingHandle`, `syncSubscription`, `syncAll`, `reportMeterEvent`, `handleWebhook`, `getStripe` | | `@delightstack/stripe/sveltekit` | `createBillingGuards` | | `@delightstack/stripe/client` | reactive Svelte 5 billing client | ## 1. Define your billing config ```typescript import { defineBillingConfig } from '@delightstack/stripe/server'; export const billing = defineBillingConfig({ secret_key: env.STRIPE_SECRET_KEY, plans: [ { id: 'pro', name: 'Pro', price: 2000, interval: 'month', entitlements: ['premium'] }, ], meters: [ { id: 'ai-tokens', name: 'AI tokens', event_name: 'ai_tokens' }, ], }); ``` ## 2. Sync products & handle webhooks ```typescript // hooks.server.ts import { createBillingHandle } from '@delightstack/stripe/server'; export const billingHandle = createBillingHandle({ config: billing, getDb: (event) => event.platform.env.DB, }); ``` `syncAll()` pushes your plans/meters to Stripe; `handleWebhook()` verifies incoming events and `syncSubscription()` reconciles them into your database. ## 3. Gate routes ```typescript // +page.server.ts import { createBillingGuards } from '@delightstack/stripe/sveltekit'; const { requirePlan } = createBillingGuards(billing); export const load = (event) => { requirePlan(event, 'pro'); // redirect / 402 if not subscribed }; ``` ## 4. Report metered usage ```typescript import { reportMeterEvent } from '@delightstack/stripe/server'; await reportMeterEvent(billing, { meter: 'ai-tokens', value: 1200, customer }); ``` **Note:** Webhook secrets and the publishable key are set as worker secrets / vars (`STRIPE_SECRET_KEY`, `PUBLIC_STRIPE_PUBLISHABLE_KEY`). See the [package README](https://github.com/brianschwabauer/delightstack/tree/main/packages/stripe). --- # Realtime (WebSocket) > Room-scoped realtime for SvelteKit on Cloudflare — reactive presence, typed events, hibernation, and live database sync. `@delightstack/websocket` is a realtime layer for SvelteKit on Cloudflare Workers — room-scoped connections, reactive presence, typed custom events, and database sync, backed by one Durable Object per room with hibernation support. ## Features - **Room-scoped** — one Durable Object per room/org; connections in a room share an instance for broadcast and presence. - **Hibernation API** — the DO sleeps between messages and recovers session state on wake. - **SharedWorker multiplexing** — a single SharedWorker holds one WebSocket per room across all tabs. - **Reactive presence** — `ws.sessions` is a Svelte 5 `$state` array, always current. - **Typed events & metadata** — pass a custom event map and session-metadata type as generics. - **Auto-reconnect** — exponential backoff with jitter and client ping keep-alive. - **Database sync** — `ws.databaseHooks()` wires entity changes into `DatabaseClient`. ## Install ```bash pnpm add @delightstack/websocket ``` | Import | Use | | --- | --- | | `@delightstack/websocket/worker` | `WebsocketServer` — the Durable Object class | | `@delightstack/websocket/server` | `createWebsocketHandle` — SvelteKit upgrade handle | | `@delightstack/websocket/client` | `WebsocketClient` — reactive Svelte 5 client | ## 1. Register the Durable Object ```typescript import { WebsocketServer } from '@delightstack/websocket/worker'; export class AppWebsocketServer extends WebsocketServer { // optional: onConnect / onDisconnect / onMessage hooks } ``` Declare it in `wrangler.toml` (see [Architecture](/getting-started/architecture/)). ## 2. Wire the SvelteKit handle ```typescript // hooks.server.ts import { createWebsocketHandle } from '@delightstack/websocket/server'; export const websocketHandle = createWebsocketHandle({ getServer: (event) => event.platform.env.WS, // authorizes via @delightstack/auth locals by default }); ``` The handle intercepts `/api/websocket` upgrade requests, authorizes them, and forwards to the DO for the connecting room. ## 3. Connect from the client ```svelte

{ws.sessions.length} online

{#each ws.sessions as session} {session.meta?.user_name} {/each} ``` `ws.sessions` updates automatically from `session:list`, `session:connected`, and `session:disconnected` events. **Tip:** Combine with [Database](/packages/database/): pass `ws.databaseHooks()` to your `DatabaseClient` so entity changes broadcast to every connected client and keep local search indexes in sync. --- # Accessibility > Built-in accessibility features in DelightStack components and best practices for building inclusive interfaces. Accessibility is not an add-on in DelightStack — it is built into every component from the start. Components use semantic HTML, proper ARIA attributes, keyboard navigation, and focus management by default. ## Built-in Features ### Semantic HTML Components render appropriate semantic elements: - `Button` renders ` ``` Form controls (`Input`, `Select`, the form `Button` heights, and the smaller controls) use a `'0'`–`'3'` subset that also drives a shared **height** — see [Control sizing](#control-sizing). ## Control sizing Form controls share one **canonical height system** so a row of them lines up. The `size` prop picks a font from the `--control-font-*` scale, and the control's height is then `font × ratio`. The density modifiers (`dense` / `comfortable`) swap the ratio. One scale → a default row is the same height, and a dense row is the same height, with no per-component hardcoding. ```css :root { /* rem so controls scale with the root font-size; even-px fonts × 0.5-step ratios keep every size on a whole-pixel height */ --control-font-0: 0.875rem; /* 14px → 42px tall */ --control-font-1: 1rem; /* 16px → 48px tall (default) */ --control-font-2: 1.125rem; /* 18px → 54px tall */ --control-font-3: 1.25rem; /* 20px → 60px tall */ --control-height-ratio: 3; /* default → 48px @ size 1 */ --control-height-ratio-dense: 2.5; /* dense → 40px @ size 1 */ --control-height-ratio-comfortable: 3.5; /* comfortable → 56px @ size 1 */ --control-pad-x: 1em; /* inline padding */ --control-pad-x-dense: 0.75em; --control-pad-x-comfortable: 1.25em; } ``` `Input`, `Select` and `Button` (including standalone icon buttons) snap to these heights: | `size` | dense | default | comfortable | | ------ | ----- | -------- | ----------- | | 0 | 35px | 42px | 49px | | **1** | 40px | **48px** | 56px | | 2 | 45px | 54px | 63px | | 3 | 50px | 60px | 70px | So a row of controls at the same `size` (and density) is the same height: ```svelte
``` ## How It Works The `Form` component creates a **form context** that child components (`Input`, `Select`, `Checkbox`, `Radio`, `Toggle`, `Range`, `Rating`) consume automatically. When a field has a `name` prop, it registers with the form context and: 1. **Reads its value** from `data[name]` 2. **Writes changes** back to `data[name]` 3. **Displays errors** from the validation result for that field 4. **Reports touched state** when the user interacts with it You do not need to wire up `bind:value` or error display manually — the form context handles it. **Tip:** Form fields still work independently outside of a `Form` component. The `name` prop only activates form context integration when a parent `Form` exists. ## Choosing a Validation Library The `Form` component accepts any validator that implements the [Standard Schema](https://github.com/standard-schema/standard-schema) interface. Here is the same validation written in three libraries: **Zod** ```typescript import { z } from 'zod'; const schema = z.object({ name: z.string().min(2, 'Name is required'), email: z.string().email('Invalid email'), age: z.number().min(18, 'Must be 18 or older'), }); ``` **Valibot** ```typescript import * as v from 'valibot'; const schema = v.object({ name: v.pipe(v.string(), v.minLength(2, 'Name is required')), email: v.pipe(v.string(), v.email('Invalid email')), age: v.pipe(v.number(), v.minValue(18, 'Must be 18 or older')), }); ``` **ArkType** ```typescript import { type } from 'arktype'; const schema = type({ name: 'string>=2', email: 'string.email', age: 'number>=18', }); ``` All three produce identical behavior when passed to the `Form` component's `schema` prop. ## Entity-Backed Forms (Database Tables) If you define your data with `Database.table()` from `@delightstack/database`, the entire form — values, validation, submission, and saving state — wires itself up from the entity: ```svelte
goto('/people')}> ``` Standalone, the input runs `parse` when it loses focus and shows the thrown error's message below the field. While the field is errored it re-validates on every keystroke, so the message clears the moment the value is fixed — without nagging the user before they finish typing. ## How Form and Field Validation Work Together Validation can live in two places, and they are designed not to conflict: 1. **Form-level schema** — the `Form` component's `schema` prop validates the whole `data` object. 2. **Field-level `parse`** — validates a single field's value. Inside a `Form`, an `Input` never runs `parse` itself. It registers the function with the form, and the form runs all field validators together with the form schema on its `validate_on` timing. The merge rule is simple: - If **both** produce an error for the same field, the **form schema's message wins**. - Fields the form schema does not cover keep their field-level error. - One error per field is ever shown. When the form schema is `table.form.schema`, the two layers are literally the same validation functions, so they always agree. ## Validation Modes The `validate_on` prop controls when fields are validated: | Mode | Behavior | | ---------- | ------------------------------------------------------------------- | | `'change'` | Validate immediately as the user types or changes a value | | `'blur'` | Validate when the user leaves the field (default) | | `'submit'` | Only validate when the form is submitted | ```svelte ...
...
``` **Note:** The `'blur'` mode is the default and works well for most forms. It provides feedback after the user finishes a field without being intrusive during typing. ## Error Display When validation fails, each field automatically displays its error message below the input. The error is associated via `aria-describedby` for screen reader accessibility. Errors are only shown for fields that have been **touched** (interacted with by the user), so a freshly loaded form does not show a wall of errors. On submit, all fields are marked as touched and all errors become visible. ## Form Context The `Form` component provides a context object that child components read. You can also access it programmatically in your own components: ```typescript import type { FormContext } from '@delightstack/components'; ``` The context provides: | Property | Type | Description | | --------------- | ---------------------------- | ---------------------------------------- | | `data` | `Record` | Current form data | | `errors` | `Record` | Validation errors by field name | | `touched` | `Record` | Which fields have been interacted with | | `is_dirty` | `boolean` | Whether any field has changed | | `is_submitting` | `boolean` | Whether submission is in progress | | `is_valid` | `boolean` | Whether all fields pass validation | | `disabled` | `boolean` | Whether the form is disabled | | `validate_on` | `'change' \| 'blur' \| 'submit'` | Current validation mode | And these methods: | Method | Description | | ---------------- | ------------------------------------- | | `setValue(name, value)` | Update a field's value | | `setTouched(name)` | Mark a field as touched | | `validateField(name)` | Validate a single field | | `register(name, el, validator?)` | Register a field element and optional field-level validator | | `unregister(name)` | Unregister a field | ## Form Props | Prop | Type | Default | Description | | ----------------- | ----------------------- | ------- | ------------------------------------------------ | | `data` | `Record` | `{}` | Form data object (bindable) | | `schema` | `StandardSchema` | — | Standard Schema validator | | `validate_on` | `'change' \| 'blur' \| 'submit'` | `'blur'` | When to validate fields | | `disabled` | `boolean` | `false` | Disable the entire form | | `reset_on_submit` | `boolean` | `false` | Reset to initial values after successful submit | | `dense` | `boolean` | `false` | Compact spacing between fields | | `comfortable` | `boolean` | `false` | Relaxed spacing between fields | ## Form Events | Event | Detail | Description | | ---------- | ----------------------------------- | ----------------------------------- | | `onsubmit` | `{ data, is_valid }` | Called on form submission | | `onchange` | `{ data, errors }` | Called when form data changes | | `onerror` | `{ errors }` | Called when validation fails on submit | ## Complete Example Here is a registration form with multiple field types, validation, and user feedback: ```svelte
``` **Tip:** The `reset_on_submit` prop resets the form data back to its initial values after a successful submission. The `onsubmit` callback supports returning a Promise — the form automatically sets `is_submitting` to `true` while it resolves, disabling the submit button. ## Using Fields Without a Form Every form component works independently when used without a parent `Form`: ```svelte
``` ### Scoped with Inline Styles For one-off overrides, use inline `style` attributes: ```svelte ``` ## Customizing Specific Token Groups ### Typography ```css :root { /* Use a custom font */ --font-sans: 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif; --font-mono: 'JetBrains Mono', ui-monospace, monospace; /* Adjust the type scale */ --text-base: 0.9375rem; /* 15px instead of 16px */ --text-lg: 1.0625rem; } ``` ### Border Radius ```css :root { /* Sharp, minimal look */ --radius-sm: 2px; --radius-md: 4px; --radius-lg: 6px; --radius-xl: 8px; --radius-2xl: 12px; } ``` ```css :root { /* Fully rounded, playful look */ --radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1rem; --radius-xl: 1.5rem; --radius-2xl: 2rem; } ``` ### Shadows ```css :root { /* Softer, more diffused shadows */ --shadow-md: light-dark( 0 4px 12px -2px rgb(0 0 0 / 0.06), inset 0 1px 0 0 rgb(255 255 255 / 0.06) ); } ``` ### Transitions ```css :root { /* Snappier animations */ --duration-fast: 75ms; --duration-normal: 150ms; --duration-slow: 200ms; /* Or slower, more deliberate */ --duration-fast: 150ms; --duration-normal: 300ms; --duration-slow: 500ms; } ``` ## Theme Switching To support runtime theme switching (beyond light/dark), use a CSS class on `:root` or ``: ```css :root.theme-ocean { --color-action: light-dark(#0284c7, #38bdf8); --color-accent: light-dark(#0891b2, #22d3ee); --color-bg: light-dark(#f0f9ff, #0c0a09); } :root.theme-forest { --color-action: light-dark(#059669, #34d399); --color-accent: light-dark(#65a30d, #a3e635); --color-bg: light-dark(#f0fdf4, #052e16); } ``` ```svelte ``` **Tip:** For light/dark mode switching specifically, use the built-in `ThemeToggle` component and the `color-scheme` property. See the [Dark Mode](/guides/dark-mode/) guide for details. --- # Alert > A streamlined confirmation dialog built on Modal, providing consistent yes/no decision flows with a programmatic API. ## Import ```svelte import { Alert } from '@delightstack/components'; ``` For the programmatic API: ```typescript import { alert } from '@delightstack/components'; ``` ## Basic Usage ```svelte ``` ## Examples ### With Icon Display an icon component above the title for visual emphasis. ```svelte ``` ### Programmatic API Create an alert dialog imperatively using the `alert()` function. It returns a `Promise` that resolves to `true` (confirmed) or `false` (cancelled). ```svelte ``` ### Promise-Aware Confirm When `oncontinue` returns a `Promise`, the confirm button automatically enters loading state until the promise resolves. ```svelte ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `open` | `boolean` | `false` | Controls visibility (`$bindable()`) | | `title` | `string` | `'Confirm'` | Alert title | | `message` | `string` | `''` | Alert message / question | | `cancel_text` | `string` | `'Cancel'` | Cancel button label | | `continue_text` | `string` | `'Continue'` | Confirm button label | | `destructive` | `boolean` | `false` | Style confirm button with `error` color | | `icon` | `Component` | `undefined` | Optional icon component displayed above the title | | `id` | `string` | auto-generated | Element ID | | `class` | `string` | `''` | Additional CSS classes | | `oncancel` | `() => void` | `undefined` | Called when Cancel is clicked | | `oncontinue` | `() => void \| Promise` | `undefined` | Called when Confirm is clicked; promise-aware (button shows loading until resolved) | ## Events | Event | Detail | Description | |-------|--------|-------------| | `oncancel` | none | Fires when the user cancels (Cancel button, backdrop click, or Escape key) | | `oncontinue` | none | Fires when the user confirms. If it returns a `Promise`, the button shows a loading state. | ## Types The component exports the `AlertOptions` interface for the programmatic API and the `alert` function: ```typescript interface AlertOptions { title?: string; message: string; cancel_text?: string; continue_text?: string; destructive?: boolean; icon?: Component; } function alert(options: AlertOptions): Promise; ``` ## Accessibility - Inherits all Modal accessibility features (`role="dialog"`, `aria-modal`, focus trap) - Keyboard navigable: Tab between Confirm and Cancel buttons - Escape key and backdrop click trigger the cancel action - When `destructive` is true, the `icon` section is styled with the error color --- # Button > A versatile, polished button with ripple effects, promise-aware loading states, dropdown menus, and confirmation flows. ## Import ```svelte import { Button } from '@delightstack/components'; ``` ## Basic Usage ```svelte ``` ## Examples ### Variants Combine variant booleans freely. The default appearance (solid action-colored) requires no variant props. ```svelte ``` ### Color Schemes Apply semantic color schemes with the `accent`, `error`, or `success` props. These combine with any variant. ```svelte ``` ### Sizes The `size` prop controls font size, which drives padding and dimensions via `em` units. ```svelte ``` ### Pill, Dense & Comfortable `dense` and `comfortable` adjust the height and padding using the shared control sizing tokens, so a Button lines up with `dense`/`comfortable` Input and Select fields in the same form row. ```svelte ``` ### Icon Buttons Set `icon` for circular icon-only buttons. Combine with any variant or badge. ```svelte ``` ### Disabled & Loading Set `loading` to show a spinner whenever you drive the busy state yourself. ```svelte ``` ### Icon Button Loading Icon buttons support the same loading feedback: the spinner (and the success checkmark) overlays the button while the icon scales away beneath it, and inherits the variant's text color. Works with the `loading` prop or a promise-returning `onclick` — try the first button. ```svelte ``` ### Promise-Aware Click Handler When `onclick` returns a `Promise`, the button manages its own loading feedback — no `loading` prop required: - **Instant actions don't flash.** The spinner only appears if the promise is still pending after ~100ms; anything faster is treated as instant. - **The spinner can't blink.** Once shown, it stays for at least ~1s, then eases out quickly. - **Success is confirmed automatically.** On resolve, a brief checkmark flashes; on reject, no checkmark is shown. Try each button below: the first shows the spinner then a checkmark, the second resolves too fast to ever show a spinner, and the third shows a spinner but no checkmark because it rejects. ```svelte ``` ### Manual Success Checkmark When you drive `loading` yourself (e.g. from a store or form), set `loading_success` to flash the same confirming checkmark as `loading` returns to `false`. Leave it off and the spinner simply disappears. ```svelte ``` ### Badge Display a notification indicator. Pass `true` for a dot or a string for a count. ```svelte ``` ### Link Button When `href` is set, the button renders as an `` element. ```svelte ``` ### Dropdown Menu Pass a `menu` snippet to render a popover dropdown when the button is clicked. ```svelte {/snippet} ``` ### Split Button with Dropdown Use the `dropdown` snippet for a secondary dropdown trigger alongside the main button action. ```svelte {/snippet} ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `size` | `'0000' \| '000' \| '00' \| '0' \| '1' \| '2' \| '3' \| '4' \| '5' \| '6'` | `undefined` | Font size tier, driving all dimensions via `em` units | | `icon` | `boolean` | `false` | Render as a circular icon-only button | | `pill` | `boolean` | `false` | Fully rounded corners | | `transparent` | `boolean` | `false` | No background or border (ghost style) | | `translucent` | `boolean` | `false` | Semi-transparent tinted background with backdrop blur | | `outline` | `boolean` | `false` | Transparent background with visible border | | `grouped` | `boolean` | `false` | Indicates the button is part of a group (border radius adjusted) | | `error` | `boolean` | `false` | Red/destructive color scheme | | `success` | `boolean` | `false` | Green/success color scheme | | `accent` | `boolean` | `false` | Accent/brand color scheme | | `overlay` | `boolean` | `false` | Dark overlay style with backdrop blur | | `dense` | `boolean` | `false` | Reduced padding/height for compact layouts | | `comfortable` | `boolean` | `false` | Increased padding/height for roomy layouts | | `full_width` | `boolean` | `false` | Stretch to container width | | `full_height` | `boolean` | `false` | Stretch to container height | | `disable_ripple` | `boolean` | `false` | Disable the ripple click effect | | `href` | `string` | `undefined` | Renders as `` instead of ` ``` ## Examples ### Toggle Group (Single Selection) Use the `active` prop on child Buttons to create a toggle group. Icon buttons rarely speak for themselves, so pass a `tooltip` to each one. Hover a button and its tooltip appears after a short delay — then slide across to the next button and its tooltip shows **instantly**, no waiting. The skip-delay window is shared by every tooltip on the page and persists briefly after you leave, so the buttons don't even need to touch. ```svelte ``` ### Variant Inheritance Set variant props on the group to apply them to all children at once. ```svelte ``` ### Vertical Orientation Stack buttons vertically instead of horizontally. ```svelte ``` ### Split Button with Dropdown Combine a primary action button with a dropdown trigger inside a group. ```svelte {/snippet} ``` ### Detached (Non-Attached) Mode Set `attached={false}` to add a small gap between buttons instead of merging borders. ```svelte ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `size` | `'0000' \| '000' \| '00' \| '0' \| '1' \| '2' \| '3' \| '4' \| '5' \| '6'` | `undefined` | Size applied to all child Buttons via context | | `outline` | `boolean` | `false` | Apply outline variant to all children | | `transparent` | `boolean` | `false` | Apply transparent variant to all children | | `translucent` | `boolean` | `false` | Apply translucent variant to all children | | `accent` | `boolean` | `false` | Apply accent color to all children | | `error` | `boolean` | `false` | Apply error color to all children | | `success` | `boolean` | `false` | Apply success color to all children | | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction | | `disabled` | `boolean` | `false` | Disable all child Buttons via context | | `attached` | `boolean` | `true` | Visually connect buttons (shared borders, merged radii) | | `id` | `string` | auto-generated | Element ID | | `class` | `string` | `''` | Additional CSS classes | | `children` | `Snippet` | `undefined` | Child Button components | ## Types The ButtonGroup exports a `ButtonGroupContext` interface used for its context contract with child Buttons: ```typescript interface ButtonGroupContext { size: | undefined | '0000' | '000' | '00' | '0' | '1' | '2' | '3' | '4' | '5' | '6'; outline: boolean; transparent: boolean; translucent: boolean; accent: boolean; error: boolean; success: boolean; disabled: boolean; } ``` Child Buttons read this context via `getContext('button-group')` and merge the group values with their own props (own props take precedence when explicitly set). ## Accessibility - `role="group"` on the container element - Provide an `aria-label` to describe the group's purpose - Buttons remain individually focusable - Hovered and focused buttons are raised above siblings via `z-index` for clean border visibility --- # CommandPalette > A keyboard-driven command interface with fuzzy search, recent commands tracking, category grouping, and match highlighting. ## Import ```svelte import { CommandPalette } from '@delightstack/components'; ``` ## Basic Usage ```svelte ``` ```svelte ``` The palette automatically registers a global `Ctrl/Cmd + K` keyboard shortcut to toggle visibility. ## Examples ### Full Command Setup ```svelte ``` ### Compact / Dense Mode Use `dense` for tighter spacing in smaller interfaces. ```svelte ``` ### Roomy / Comfortable Mode Use `comfortable` for more generous spacing and larger tap targets — handy on touch interfaces or when the palette is the primary surface. ```svelte ``` ### Without Category Grouping Disable grouping to show a flat list of results. ```svelte ``` ### With Selection Callback Track when any command is selected using the `onselect` prop. ```svelte { analytics.track('command_used', { id: command.id }); }} /> ``` ### Promise-Aware Commands When a command's `onselect` returns a `Promise`, a spinner appears while executing. On success, the palette closes. On failure, it stays open. ```svelte const commands = [ { id: 'sync', title: 'Sync Data', description: 'Synchronize with the server', onselect: async () => { await api.syncAll(); } } ]; ``` ### Custom Placeholder ```svelte ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `open` | `boolean` | `false` | Controls visibility (`$bindable()`) | | `commands` | `CommandOption[]` | `[]` | Available commands to search | | `placeholder` | `string` | `'Type a command or search...'` | Input placeholder text | | `recent_limit` | `number` | `5` | Number of recent items to show when input is empty | | `group_by` | `'category' \| 'none'` | `'category'` | How to group results | | `dense` | `boolean` | `false` | Compact result item spacing | | `comfortable` | `boolean` | `false` | Roomy result item spacing | | `onselect` | `(command: CommandOption) => void` | `undefined` | Called when any command is selected | | `id` | `string` | auto-generated | Element ID | | `class` | `string` | `''` | Additional CSS classes | ## Events | Event | Detail | Description | |-------|--------|-------------| | `onselect` | `CommandOption` | Fires when a command is selected, before executing `command.onselect` | ## Types ```typescript interface CommandOption { /** Unique identifier for the command */ id: string; /** Display title of the command */ title: string; /** Optional description shown below the title */ description?: string; /** Category for grouping commands */ category?: string; /** Optional icon component */ icon?: Component; /** Keyboard shortcut keys to display (e.g. ['Ctrl', 'K']) */ shortcut?: string[]; /** Additional search keywords */ keywords?: string[]; /** Whether the command is disabled */ disabled?: boolean; /** Called when the command is selected */ onselect: () => void | Promise; } ``` ## Keyboard Navigation | Key | Action | |-----|--------| | `Ctrl/Cmd + K` | Toggle open (global shortcut) | | `Arrow Up` / `Arrow Down` | Navigate results | | `Enter` | Execute selected command | | `Escape` | Close palette | ## Fuzzy Search The palette uses a built-in fuzzy search engine that scores matches across `title`, `description`, `category`, and `keywords` fields. Matching characters in the title are highlighted in the results. Scoring priority: exact match > starts with > word boundary match > contains > fuzzy character match. Title matches are weighted highest, followed by keywords, description, and category. ## Recent Commands Recently used commands are tracked in memory and shown when the input is empty. The list is sorted by recency and limited by the `recent_limit` prop. ## Accessibility - ARIA combobox pattern (`role="combobox"` on input, `role="listbox"` on results) - `aria-activedescendant` tracks the highlighted result - `aria-expanded` reflects whether results are visible - `aria-autocomplete="list"` on the input - `role="option"` with `aria-selected` and `aria-disabled` on each result item - Focus trap keeps focus within the palette while open - Input is always focused while the palette is open --- # ContextMenu > A right-click context menu system using the {@attach contextMenu()} Svelte attachment API with Popover-based positioning and List rendering. ## Import ```svelte import { contextMenu } from '@delightstack/components'; ``` Also import the `ContextMenu` component itself (typically in your root layout) to mount the global context menu renderer: ```svelte import { ContextMenu } from '@delightstack/components'; ``` ## Basic Usage Attach a context menu to any element using `{@attach contextMenu()}`. The `ContextMenu` component must be mounted somewhere in your component tree (usually in your root layout). ```svelte
editItem() }, { label: 'Duplicate', onclick: () => duplicateItem() }, { label: 'Delete', onclick: () => deleteItem() }, ] })}> Right-click in this area
``` ```svelte
editItem() }, { label: 'Delete', onclick: () => deleteItem() } ] })}> Right-click me
``` ## Examples ### With Icons Pass a Svelte component to an action's `icon` property to render it before the label. ```svelte
lastAction = 'Edit clicked' }, { label: 'Copy', icon: CopyIcon, onclick: () => lastAction = 'Copy clicked' }, { label: 'Delete', icon: DeleteIcon, onclick: () => lastAction = 'Delete clicked' }, ] })}> Right-click for file options
``` ### With Links Actions can navigate using `href` and `target` instead of `onclick`. ```svelte
Right-click for navigation options
``` ### With Active State and Disabled Items ```svelte
viewMode = 'grid' }, { label: 'List View', active: viewMode === 'list', onclick: () => viewMode = 'list' }, { label: 'Table View', disabled: true }, ] })}> Right-click to change view (current: {viewMode})
``` ### Custom Rendered Items Set an action's `snippet` property to render custom content instead of a plain `label`. Each action renders its own snippet, so you can compose icons, secondary text, shortcut hints, or any markup you like. ```svelte
lastAction = 'Shared via email' }, { snippet: linkItem, onclick: () => lastAction = 'Link copied' }, { snippet: embedItem, onclick: () => lastAction = 'Embed code copied' }, ] })}> Right-click to share
{#snippet row(emoji, title, hint)} {emoji} {title} {hint} {/snippet} {#snippet emailItem()}{@render row('✉️', 'Email', '⌘E')}{/snippet} {#snippet linkItem()}{@render row('🔗', 'Copy link', '⌘C')}{/snippet} {#snippet embedItem()}{@render row('🧩', 'Embed', '⌘B')}{/snippet} ``` ### Promise-Aware Actions When an action's `onclick` returns a Promise, the clicked item shows a loading spinner while it is pending (after a short delay, so fast actions never flash one), and the menu stays open until the promise resolves. ```svelte
status = 'Renamed' }, ] })}> Right-click and choose "Sync to cloud"
``` ## API ### `contextMenu()` Attachment The `contextMenu` function is a Svelte attachment that registers context menu options for an element. It uses a `WeakMap` internally for garbage-collectible associations. ```svelte
Right-click target
``` ### ContextMenuOptions ```typescript interface ContextMenuOptions { actions: Array<{ onclick?: (event: PointerEvent) => void | Promise; href?: string; target?: '_blank' | '_self' | '_parent' | '_top'; active?: boolean; label?: string; disabled?: boolean; icon?: Component; snippet?: Snippet<[ContextMenuOptions & { el: HTMLElement }]>; }>; } ``` ## Props The `ContextMenu` component itself has no props -- it is a global singleton that listens for `contextmenu` events on the window and renders the appropriate menu via Popover. ## Types ```typescript interface ContextMenuOptions { actions: Array<{ onclick?: (event: PointerEvent) => void | Promise; href?: string; target?: '_blank' | '_self' | '_parent' | '_top'; active?: boolean; label?: string; disabled?: boolean; icon?: Component; snippet?: Snippet<[ContextMenuOptions & { el: HTMLElement }]>; }>; } ``` ## Accessibility - Menu items are rendered using `List` and `ListItem` components with proper semantics - Keyboard activation on menu items via Enter/Space - Escape closes the context menu - The menu closes on scroll to prevent stale positioning - Context tracking uses a `WeakMap` for automatic cleanup when elements are removed from the DOM --- # Modal > A polished dialog overlay with smooth animations, focus trapping, backdrop blur, and crossfade transitions. ## Import ```svelte import { Modal } from '@delightstack/components'; ``` ## Basic Usage ```svelte

This is a modal dialog with some example content. Click outside or press Escape to close.

``` ## Examples ### With Footer Actions ```svelte

Update your profile information below.

{#snippet footer()} {/snippet}
``` ### Custom Header Replace the entire header with a custom snippet. ```svelte {#snippet header()}

Settings

Configure your preferences
{/snippet}

Settings content goes here.

``` ### Header Start and End Slots Add content before the title or after it (before the close button). ```svelte {#snippet header_start()} Draft {/snippet} {#snippet header_end()} {/snippet}

Document content...

``` ### Custom Width and Height Override the default sizing with explicit CSS values. ```svelte

This modal has a custom size of 800x500px.

``` ### Non-Closable Modal Prevent closing via Escape key and backdrop click for required flows. ```svelte

You must complete this step before continuing.

{#snippet footer()} {/snippet}
``` ### Transition from Element Animate the modal from a specific trigger element for a connected spatial experience. ```svelte

This modal animated from the button.

``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `open` | `boolean` | `false` | Controls visibility (`$bindable()`) | | `title` | `string` | `''` | Modal title text | | `closable` | `boolean` | `true` | Allow closing via Escape key and backdrop click | | `disable_close_icon` | `boolean` | `false` | Hide the X close button | | `modal_id` | `string` | `''` | ID used for transition target tracking | | `width` | `string` | `''` | Explicit CSS width (desktop) | | `height` | `string` | `''` | Explicit CSS height (desktop) | | `max_width` | `string` | `'calc(100vw - 2rem)'` | Maximum width | | `max_height` | `string` | `'calc(100svh - 2rem)'` | Maximum height | | `transition_target` | `HTMLElement \| Element` | `undefined` | Element to animate from when opening | | `style` | `string` | `''` | Inline CSS styles | | `class` | `string` | `''` | Additional CSS classes | | `children` | `Snippet` | `undefined` | Main modal content | | `header` | `Snippet` | `undefined` | Replace entire header | | `header_start` | `Snippet` | `undefined` | Content before the title | | `header_end` | `Snippet` | `undefined` | Content after the title (before close button) | | `footer` | `Snippet` | `undefined` | Footer content area | | `footer_start` | `Snippet` | `undefined` | Left side of footer | | `footer_end` | `Snippet` | `undefined` | Right side of footer | | `onopen` | `() => void` | `undefined` | Called when the modal opens | | `onclose` | `() => boolean \| undefined \| void` | `undefined` | Called when the modal closes. Return `false` to prevent closing. | | `onbackdropclick` | `() => void` | `undefined` | Called when the backdrop is clicked | ## Events | Event | Detail | Description | |-------|--------|-------------| | `onopen` | none | Fires when the modal mounts | | `onclose` | none | Fires when the modal is being closed. Return `false` to prevent it. | | `onbackdropclick` | none | Fires when the backdrop overlay is clicked | ## Accessibility - `role="dialog"` with `aria-modal="true"` - `aria-labelledby` pointing to the title element - `aria-describedby` pointing to the body content - Focus trap via `focusTrap` from `@delightstack/utilities` (Tab cycles within modal only) - Escape key dismissal (when `closable` is true) - Body scroll is locked while the modal is open (`html { overflow: hidden }`) - Backdrop has a blur effect for visual depth --- # Popover > A floating content container with smart Floating UI positioning, multiple trigger modes, focus trapping, and hover trapezoid for safe diagonal mouse movement. ## Import ```svelte import {Popover} from '@delightstack/components'; ``` ## Basic Usage ```svelte

Popover Title

Positioned automatically using Floating UI.

Click outside to dismiss.

``` ## Examples ### Popover State Demo Control the state of the popover externally ```svelte
  • Suggestion 1
  • Suggestion 2
  • Suggestion 3
``` ### Dense and Comfortable Control the padding with `dense` or `comfortable` props. ```svelte Compact Default Spacious ``` ## Props | Prop | Type | Default | Description | | --------------------- | ----------------------- | -------------- | ------------------------------------------------------------------- | | `ref_element` | `HTMLElement` | `undefined` | Element to position relative to | | `opened` | `boolean` | `false` | Controls visibility (`$bindable()`) | | `placement` | `Placement` | `'bottom'` | Preferred Floating UI placement | | `strategy` | `'absolute' \| 'fixed'` | `'fixed'` | CSS positioning strategy. `'fixed'` renders through a Portal. | | `arrow` | `boolean` | `true` | Show arrow pointer toward the trigger | | `x` | `number` | `undefined` | X coordinate for virtual positioning (used instead of `ref_element`) | | `y` | `number` | `undefined` | Y coordinate for virtual positioning (used instead of `ref_element`) | | `open_on_hover` | `boolean` | `false` | Open when the reference element is hovered | | `open_on_click` | `boolean` | `false` | Open when the reference element is clicked | | `open_on_focus` | `boolean` | `false` | Open when the reference element receives focus | | `close_on_outside_click` | `boolean` | `true` | Close when clicking outside the popover | | `close_on_inside_click` | `boolean` | `false` | Close when a button-like element inside is clicked | | `close_on_escape_key` | `boolean` | `true` | Close on Escape key | | `disable_initial_focus` | `boolean` | `false` | Do not auto-focus content when opened | | `hover_delay` | `number` | `100` | Delay in ms before opening on hover | | `dense` | `boolean` | `false` | Reduced padding (`0.5rem 0.75rem`) | | `comfortable` | `boolean` | `false` | Increased padding (`1.5rem 2rem`) | | `radius` | `string` | `undefined` | Border radius override via `--popover-radius` | | `children` | `Snippet` | `undefined` | Popover content | | `id` | `string` | auto-generated | Element ID | | `class` | `string` | `''` | Additional CSS classes | | `style` | `string` | `''` | Inline CSS styles | ## Events | Event | Detail | Description | | ------------------- | --------- | ----------------------------------------------------- | | `opened` (bindable) | `boolean` | Reactive state reflecting whether the popover is open | ## Accessibility - Focus trap via `focusTrap` from `@delightstack/utilities` keeps Tab cycling within the popover - Escape key closes the popover (respects nested popovers -- closes innermost first) - Focus returns to the trigger element on close - Keyboard activation on the trigger element via Enter/Space (when `open_on_click` or `open_on_focus` is used) - Proper `z-index` stacking with `data-popover-index` for nested popovers --- # Portal > A utility component that renders children in a different DOM location, escaping parent overflow and stacking contexts. ## Import ```svelte import { Portal } from '@delightstack/components'; ``` The `portal` action is also available for direct element usage: ```svelte import { portal } from '@delightstack/components'; ``` ## Basic Usage ```svelte
This content renders in the .portals container at the end of body.
``` ## Examples ### Default Target (.portals) By default, Portal creates (or reuses) a `.portals` container at the end of ``: ```svelte
This escapes any overflow:hidden ancestors.
``` The resulting DOM structure: ```html
``` ### Custom CSS Selector Target Specify a CSS selector to portal content to a specific element. ```svelte
Saved successfully
``` ### HTMLElement Target Pass a direct DOM element reference as the target. ```svelte

Content portaled into the custom container.

``` ### Svelte Action Usage The `portal` function is also exported as a Svelte action for use with `use:portal`: ```svelte
Content moved to #my-target
``` ### Escaping Overflow Containers A common use case is rendering floating content that would otherwise be clipped by a parent with `overflow: hidden`. ```svelte

This container clips its children.

``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `target` | `string \| HTMLElement` | `'.portals'` | Destination element or CSS selector | | `children` | `Snippet` | `undefined` | Content to portal | | `id` | `string` | auto-generated | Element ID for the portal wrapper | ## How It Works 1. Children are rendered normally in the Svelte component tree (reactivity stays intact). 2. On mount, the DOM nodes are moved to the target element using `use:portal`. 3. Svelte reactivity, event handlers, and bindings continue to work across the portal boundary. 4. On unmount, nodes are removed from the target and cleaned up. The portal wrapper uses `display: contents` so it does not affect layout at the target location. ## Accessibility - Maintains proper reactivity and event handling across the portal boundary - DOM order should match visual order when possible - Combine with `focusTrap` from `@delightstack/utilities` for modal-like portaled content - Screen readers follow DOM order, so portaled content appears at the portal target location :::note Most overlay components (Modal, Popover, CommandPalette) handle portaling internally. Direct Portal usage is typically only needed for custom overlay patterns or when building new overlay components. ::: --- # ThemeToggle > A theme switcher that cycles between light, dark, and auto modes with animated sun-to-moon icon morphing and localStorage persistence. ## Import ```svelte import { ThemeToggle } from '@delightstack/components'; ``` ## Basic Usage ```svelte ``` ## Examples ### Bindable Theme State Bind to the `theme` prop to read or control the current theme preference. ```svelte

Current theme: {theme}

``` ### Two-State Toggle (No Auto) Disable the system/auto option to cycle only between light and dark. ```svelte ``` ### With a Text Label Set `show_label` to display the current mode name ("Light" / "Dark" / "Auto") beside the icon. Pass `label` to override the text with your own. ```svelte ``` ### Detecting Effective Dark Mode Bind to `is_dark` to get the resolved dark state (taking system preference into account when theme is `'auto'`). ```svelte {#if is_dark}

Dark mode is active

{:else}

Light mode is active

{/if} ``` ### In a Header / Navbar Use a smaller size to fit in navigation bars. ```svelte
``` ### Reacting to Changes Use the `onchange` callback to run logic when the theme changes. ```svelte { console.log('Theme changed to:', newTheme); }} /> ``` ### Custom Tooltip Override the default tooltip text. ```svelte ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Current theme preference (`$bindable()`) | | `is_dark` | `boolean` | `false` | Computed resolved dark state (`$bindable()`, read-only). True when effective appearance is dark. | | `size` | `'0' \| '1' \| '2'` | `'1'` | Component size (`'0'` = 18px icon, `'1'` = 24px icon, `'2'` = 32px icon) | | `disable_auto` | `boolean` | `false` | Hide the auto/system option (two-state toggle only) | | `show_label` | `boolean` | `false` | Show a text label beside the icon ("Light" / "Dark" / "Auto") | | `label` | `string` | `undefined` | Custom label text override. Falls back to the current mode name when `show_label` is set. | | `tooltip` | `string` | `''` | Override the default tooltip text. Defaults to the `aria-label` value. | | `id` | `string` | `undefined` | Element ID | | `class` | `string` | `''` | Additional CSS classes | | `onchange` | `(theme: 'light' \| 'dark' \| 'auto') => void` | `undefined` | Called when the theme changes | ## Events | Event | Detail | Description | |-------|--------|-------------| | `onchange` | `'light' \| 'dark' \| 'auto'` | Fires after cycling to a new theme state | ## Theme Cycle Order | Mode | Cycle | |------|-------| | Three-state (default) | `light` -> `dark` -> `auto` -> `light` | | Two-state (`disable_auto`) | `light` -> `dark` -> `light` | ## Theme Application When the theme changes, the component: 1. Sets `document.documentElement.style.colorScheme` to `'light'` or `'dark'` based on the effective dark state. 2. Saves the preference to `localStorage` under the key `'delightstack:theme'`. 3. Watches for system preference changes via `matchMedia('(prefers-color-scheme: dark)')` and updates `is_dark` reactively when theme is `'auto'`. ### Anti-Flash Script To prevent a flash of wrong theme on page load, add the following inline script to your HTML `` (runs synchronously before the body renders): ```html ``` ## Accessibility - Renders as a `

Additional information that can be expanded and collapsed smoothly.

The height animates from 0 to auto without any JavaScript measurement.

``` ## Examples ### Toggle with a Button ```svelte

This content slides in and out smoothly.

``` ### Conditional Sections ```svelte
``` ### Always Expanded A simple example showing the Expand container in its open state. ```svelte
This content is visible because show is true.
``` ### With Custom Inline Style ```svelte

This expand container has a custom inline style with an accent border and tinted background.

``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `show` | `boolean` | `false` | Controls expanded/collapsed state, bindable | | `style` | `string` | `''` | Additional inline styles | | `children` | `Snippet` | - | Content to expand/collapse | ## How It Works The component uses the CSS Grid trick for animating height to/from auto: - **Collapsed**: `grid-template-rows: min-content 0fr` with `opacity: 0` - **Expanded**: `grid-template-rows: min-content 1fr` with `opacity: 1` - A `::before` pseudo-element with `min-content` row prevents layout collapse - Content uses `visibility: hidden` when collapsed and `visibility: visible` when expanded This approach provides smooth height animation without any JavaScript measurement of content height. ## Accessibility - Uses the `inert` attribute when collapsed to prevent focus trapping in hidden content - Screen readers skip collapsed content entirely (content is not focusable or readable when hidden) - No explicit ARIA attributes are needed -- the `inert` attribute handles accessibility - Content opacity fades alongside height for a polished transition - Collapsed state takes zero space in the layout --- # List > A flexible container for list items with selection handling, multiple interaction modes, and consistent styling. ## Import ```svelte import { List, ListItem } from '@delightstack/components'; ``` ## Basic Usage ```svelte console.log('clicked')}>Action Item console.log('clicked')}>Another Action ``` ## Examples ### Appearance The list is **transparent** by default, so it composes onto any surface (a card, a sidebar, a popover) without imposing its own. Add `filled` for a subtle surface or `outline` for a bordered card — both restore the visible rounded corners, and the two can be combined. The item hover/active highlights stay rounded in every mode. ```svelte Profile Settings Sign out ``` ### Radio Selection Single-select mode using radio buttons. Bind `value` to track the selected index. ```svelte Small Medium Large ``` ### Checkbox Multi-Select Multiple-select mode using checkboxes. The `value` array contains all selected indices. ```svelte Notifications Email Updates SMS Alerts ``` ### Toggle Multi-Select The same multi-select behavior as `checkbox`, rendered with toggle switches — a natural fit for settings lists. ```svelte Wi-Fi Bluetooth Airplane Mode ``` ### Spacing `dense` tightens the rows (great for action menus); `comfortable` relaxes them. The default sits between the two and lines up with the standard control height. ```svelte handleEdit()}>Edit handleDuplicate()}>Duplicate handleDelete()}>Delete ``` ### Text Mode (Non-Interactive) Display-only list with no hover effects or click handlers. ```svelte Read-only item Another item ``` ### Skeleton Loading ```svelte ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `type` | `'button' \| 'text' \| 'radio' \| 'checkbox' \| 'toggle'` | `'button'` | Interaction mode for items | | `value` | `number[]` | `[]` | Selected item indices (bindable) | | `dense` | `boolean` | `false` | Compact spacing | | `comfortable` | `boolean` | `false` | Relaxed spacing | | `filled` | `boolean` | `false` | Render on a subtle filled surface with rounded corners | | `outline` | `boolean` | `false` | Render with a 1px border + rounded corners (transparent fill); combine with `filled` for a card | | `disabled` | `boolean` | `false` | Disable all items | | `touched` | `boolean` | `false` | Whether the user has interacted (bindable) | | `padding_x` | `string` | - | Horizontal padding override (e.g. `'16px'`) | | `padding_y` | `string` | - | Vertical padding override (e.g. `'16px'`) | | `skeleton` | `boolean` | `false` | Show loading skeleton items | | `skeleton_count` | `number` | `5` | Number of skeleton items to show | | `id` | `string` | auto | Element ID | | `class` | `string` | `''` | Additional CSS classes | | `style` | `string` | `''` | Additional inline styles | | `children` | `Snippet` | - | ListItem children | ## Events | Event | Detail | Description | |-------|--------|-------------| | `ontouch` | `() => void` | Called when the first interaction occurs | | `onchange` | `(value: number[]) => void` | Called when selection changes | ## Types The List component exports a `ListContext` interface used for parent-child communication via Svelte context: ```typescript interface ListContext { type: 'button' | 'text' | 'radio' | 'checkbox' | 'toggle'; value: number[]; dense: boolean; comfortable: boolean; disabled: boolean; level: number; id: string; } ``` ## Accessibility - Renders as a `