solid-sonner
An opinionated toast notification component for Solid, closely tracking the React Sonner API.
Repository Health
Technical Analysis
solid-sonner is a SolidJS port of Sonner, the popular React toast library, giving Solid apps the same imperative toast() API, animations, and swipe-to-dismiss behavior without pulling in React. It ships as a single drop-in <Toaster /> component plus a toast() function with success/error/warning/info/loading/promise/custom variants, a useSonner() hook for reading active toasts, and full styling control via classNames, unstyled mode, and Tailwind-friendly overrides.
The project deliberately mirrors upstream Sonner’s behavior and bug fixes release-for-release — its changelog cites the specific upstream commits it ports — so teams already using Sonner in a React codebase can reuse the same mental model and most of the same props when building with Solid.
What You Get
- Drop-in Toaster component - Mount
<Toaster />once and calltoast()anywhere in the tree; no context provider or store wiring required. - Full toast type coverage -
toast.success,.error,.warning,.info,.loading,.promise, and.customcover the common notification shapes out of the box. - Promise-aware toasts -
toast.promise()automatically walks a toast through loading to success/error states, including HTTP-response and Error-aware resolution. - Swipe-to-dismiss with configurable directions - Toasts respond to pointer drags with velocity-based thresholds and per-position default swipe directions.
- Multiple toaster instances - Toasts can be routed to a specific
<Toaster id="..." />viatoasterId, useful for a sidebar-scoped toast feed alongside a global one. - Unstyled / Tailwind-friendly mode -
toastOptions.unstyledplus a fullclassNamesmap lets you replace the shipped CSS entirely.
Common Use Cases
- Form submission feedback - Show a loading toast while a form submits, then update it to success or error in place using the same toast id.
- Background job / upload progress - Use
toast.promise()to reflect a long-running async task’s state without manual toast bookkeeping. - Undo flows - Attach an
actionwith a label andonClickto a toast so users can undo a destructive action, Gmail-style. - Multi-region notifications - Run a global
<Toaster />plus a secondary scoped<Toaster id="sidebar" />to separate app-wide alerts from panel-local ones.
Under The Hood
Architecture The library is a single-module port of the React Sonner toast library to SolidJS, contained almost entirely in src/index.tsx alongside a small state.ts observer and a primitives.ts hook. State flows through a class-based Observer (ToastState) acting as a lightweight pub/sub store: toast(), toast.success(), etc. call into ToastState.create()/dismiss(), which publishes plain toast objects to subscribers. The Toaster component subscribes to this observer inside onMount and mirrors incoming toast events into a SolidJS createStore, using produce/reconcile to keep per-toast identity stable across updates. Each visual Toast is rendered by a dedicated child component that owns its own drag/swipe/timer signals, with createEffect-driven auto-dismiss timers gated by page-visibility and hover/interacting state. Positioning is derived from memoized position groupings, and per-toast vertical offset is computed from a shared heights signal array that every Toast writes into on mount and resize. There is no dependency injection or plugin system; the core abstraction is the Observer singleton, and changing its shape would break every toast() call site.
Tech Stack
TypeScript plus SolidJS (peer dependency ^1.6.0) is the only runtime dependency; the package ships no other non-peer dependencies and no CSS framework, injecting a plain stylesheet at runtime via a <style data-sonner> tag. The dev workflow runs on Vite and vite-plugin-solid through vite-plus’s vp CLI wrapper (vp pack for building the npm package, vp lint/fmt/check for tooling), with TypeScript providing types. Testing uses Playwright driving a small SolidJS demo app rather than isolated unit tests. Releases are managed with Changesets, and the package is distributed as ESM only, with an exports map exposing a solid condition alongside compiled JS and declaration files.
Code Quality
The only tests are Playwright end-to-end specs that click through a demo app’s data-testid buttons and assert on rendered toast DOM — there are no isolated unit tests of the Observer logic in state.ts, though the e2e coverage does exercise dismiss/promise/loading/custom toast flows. Source is fully typed TypeScript with a complete public type surface for toast and toaster props, and inline comments explain non-obvious ordering, such as why pending dismissals are tracked and why history trimming only drops already-dismissed toasts. Linting and formatting are delegated to the vp CLI, a typecheck script runs the compiler in no-emit mode, and CI runs via GitHub Actions.
API Design
The library’s main achievement is faithfully reproducing React Sonner’s full imperative API — toast(), toast.success/error/promise/custom, useSonner() — on top of Solid’s fine-grained reactivity instead of React’s re-render model, including edge cases like reusing a dismissed toast’s id, updating a loading toast in place, and swipe-to-dismiss physics with velocity-based thresholds, several of which are called out explicitly in the changelog as ports of upstream bug fixes. Getting started requires only rendering <Toaster /> once and calling toast() from anywhere, with no configuration; the API intentionally mirrors Sonner’s so migrating a React app’s toast usage to Solid is close to a drop-in replacement. It does not introduce new toast concepts beyond what Sonner already established — its value is DX parity and low adoption friction for teams moving between the two frameworks.
Used by 2 apps in this directory
Enclosed
Security
Send end-to-end encrypted notes and files where the server never sees your content — true zero-knowledge sharing.
Papra
Bookmarks Archiving
Self-hosted document archiving with email ingestion, OCR full-text search, and pluggable storage — store once, find anything.