Context
The current Sonner integration relies on three JS/CSS files loaded globally:
| File |
Size |
Role |
public/components/sonner.js |
~35KB |
Third-party Sonner library — renders toasts into [data-sonner-toaster] |
public/components/sonner.min.js |
~14KB |
Minified version |
public/components/sonner.css |
~8.6KB |
Sonner styles |
public/components/lazy_load_sonner.js |
~4KB |
Lazy loader — defers loading sonner.js until SonnerTrigger or SonnerList appears in DOM |
The Leptos side (app_crates/registry/src/ui/sonner.rs) provides:
SonnerTrigger — a button with data-toast-title, data-toast-description, data-toast-position, data-variant attributes. Sonner.js reads these on click and renders a toast.
SonnerList / SonnerContainer / SonnerToaster — the DOM container where Sonner.js injects toast elements.
The JS library owns all toast state: creation, stacking, animation, auto-dismiss timers, and swipe-to-dismiss. Leptos has no visibility into this state.
What needs to happen
Replace the entire JS layer with a pure Rust/Leptos implementation.
A toast_custom module already exists at app_crates/registry/src/ui/toast_custom/ and appears to be an in-progress Rust toast system — use this as the foundation rather than starting from scratch. Audit what it already covers before building anything new.
Core requirements
1. Global toast signal
A RwSignal<Vec<ToastData>> (or similar) provided via Leptos context, accessible anywhere without prop drilling:
// Call from anywhere in the component tree
show_toast().success("Saved!");
show_toast().error("Something went wrong");
show_toast().with_description("Title", "Description");
2. SonnerTrigger → pure Rust click handler
Replace the data-* attribute pattern + JS click interception with a Leptos on:click:
// Before: JS reads data-toast-title on click
// After: Rust calls show_toast() directly
<button on:click=move |_| show_toast().push(toast)>
{children()}
</button>
3. SonnerToaster → reactive Leptos renderer
SonnerToaster reads the signal and renders toasts as Leptos components — no JS injection into the DOM.
4. Auto-dismiss timers
Use set_timeout (from leptos::leptos_dom::helpers or gloo-timers) to auto-remove toasts after a configurable duration.
5. Animations
CSS-only enter/exit animations (no JS animation engine). Sonner's CSS can serve as reference; the goal is to replicate the visual feel without the library.
6. Variants
Preserve all existing ToastType variants: Default, Success, Error, Warning, Info, Loading.
7. Positions
Preserve all SonnerPosition variants: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight.
Files to delete
public/components/sonner.js
public/components/sonner.min.js
public/components/sonner.css
public/components/lazy_load_sonner.js
Files to update
app/src/shell.rs — remove sonner CSS preload/load tags
app_crates/registry/src/ui/sonner.rs — rewrite using Leptos signals
app_crates/registry/src/demos/demo_sonner.rs / demo_sonner_variants.rs / demo_sonner_positions.rs
public/registry/styles/default/sonner.md — update registry snapshot
Testing
A comprehensive e2e test suite already exists at e2e/tests/components/sonner.spec.ts (355KB). Run it against the Rust implementation to verify behavioral parity before closing this issue. All existing scenarios must pass.
Reference
Context
The current Sonner integration relies on three JS/CSS files loaded globally:
public/components/sonner.js[data-sonner-toaster]public/components/sonner.min.jspublic/components/sonner.csspublic/components/lazy_load_sonner.jssonner.jsuntilSonnerTriggerorSonnerListappears in DOMThe Leptos side (
app_crates/registry/src/ui/sonner.rs) provides:SonnerTrigger— a button withdata-toast-title,data-toast-description,data-toast-position,data-variantattributes. Sonner.js reads these on click and renders a toast.SonnerList/SonnerContainer/SonnerToaster— the DOM container where Sonner.js injects toast elements.The JS library owns all toast state: creation, stacking, animation, auto-dismiss timers, and swipe-to-dismiss. Leptos has no visibility into this state.
What needs to happen
Replace the entire JS layer with a pure Rust/Leptos implementation.
A
toast_custommodule already exists atapp_crates/registry/src/ui/toast_custom/and appears to be an in-progress Rust toast system — use this as the foundation rather than starting from scratch. Audit what it already covers before building anything new.Core requirements
1. Global toast signal
A
RwSignal<Vec<ToastData>>(or similar) provided via Leptos context, accessible anywhere without prop drilling:2. SonnerTrigger → pure Rust click handler
Replace the
data-*attribute pattern + JS click interception with a Leptoson:click:3. SonnerToaster → reactive Leptos renderer
SonnerToasterreads the signal and renders toasts as Leptos components — no JS injection into the DOM.4. Auto-dismiss timers
Use
set_timeout(fromleptos::leptos_dom::helpersorgloo-timers) to auto-remove toasts after a configurable duration.5. Animations
CSS-only enter/exit animations (no JS animation engine). Sonner's CSS can serve as reference; the goal is to replicate the visual feel without the library.
6. Variants
Preserve all existing
ToastTypevariants:Default,Success,Error,Warning,Info,Loading.7. Positions
Preserve all
SonnerPositionvariants:TopLeft,TopCenter,TopRight,BottomLeft,BottomCenter,BottomRight.Files to delete
public/components/sonner.jspublic/components/sonner.min.jspublic/components/sonner.csspublic/components/lazy_load_sonner.jsFiles to update
app/src/shell.rs— remove sonner CSS preload/load tagsapp_crates/registry/src/ui/sonner.rs— rewrite using Leptos signalsapp_crates/registry/src/demos/demo_sonner.rs/demo_sonner_variants.rs/demo_sonner_positions.rspublic/registry/styles/default/sonner.md— update registry snapshotTesting
A comprehensive e2e test suite already exists at
e2e/tests/components/sonner.spec.ts(355KB). Run it against the Rust implementation to verify behavioral parity before closing this issue. All existing scenarios must pass.Reference
app_crates/registry/src/ui/toast_custom/— existing Rust toast foundation, audit this first