Sonner
An opinionated toast manager for Rails apps.
Recommended toast API
Realistic app events using the page-level Sonner toaster.
Installation
Add the Sonner unit to your app:
rails generate shadcn:add sonner
Usage
Render the toaster once, usually in your application layout. Use Sonner for new toast work. The older Toast component remains available for low-level markup and compatibility.
<%= render Shadcn::SonnerComponent.new %>
import { toast } from "shadcn-rails-stimulus"
toast("Invoice created", {
description: "Invoice #1042 was created for Acme Studio.",
variant: "success",
action: {
label: "Undo",
onClick: () => rollbackInvoice()
}
})
toast("Payment failed", {
description: "The card was declined. Ask the customer for another payment method.",
variant: "destructive",
duration: 8000
})
const savingId = toast("Saving customer", { description: "Updating billing contact...", duration: 0 })
submitCustomerForm().then(() => toast.dismiss(savingId))
Examples
With action
Add a secondary action so users can undo a toast-driven change.
import { toast } from "shadcn-rails-stimulus"
toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
action: {
label: "Undo",
onClick: () => console.log("Undo")
}
})
Positions
Choose where the viewport is pinned.
<%= render Shadcn::Sonner.new(id: "sonner-top-center-viewport", position: :top_center, duration: 5000) do %>
<%= button_tag "Publish post", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Post published", description: "The changelog is live and subscribers were notified.", variant: "info" } %>
<% end %>
Stack limit
Keep the visible stack small by setting a limit.
<%= render Shadcn::SonnerComponent.new(id: "sonner-limit-viewport", limit: 2, duration: 8000) do %>
<div class="flex flex-wrap gap-2">
<%= button_tag "Create draft", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Draft created" } %>
<%= button_tag "Save edits", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Proposal saved", variant: "success" } %>
<%= button_tag "Show error", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Sync failed", variant: "destructive" } %>
</div>
<% end %>
Persistent toast
Set duration to 0 for a toast that only closes when dismissed.
<%= render Shadcn::Toaster.new(id: "sonner-persistent-viewport", duration: 0) do %>
<%= button_tag "Save profile", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Profile saved", description: "Avatar, display name, and notification settings were saved.", variant: "success", duration: 0 } %>
<% end %>
Turbo Streams
The viewport is persistent and has a stable id, so a Turbo Stream can append server-rendered toast markup. The Sonner controller initializes appended nodes, applies missing classes, starts timers, and adds a dismiss button.
<%= turbo_stream.append "shadcn-sonner-viewport" do %>
<li data-sonner-toast="true"
data-variant="destructive"
data-duration="8000">
Failed to save changes.
</li>
<% end %>
API Reference
SonnerComponent
| Prop | Type | Default | Description |
|---|---|---|---|
| position |
Symbol, String
|
:bottom_right
|
Viewport position: top_left, top_center, top_right, bottom_left, bottom_center, bottom_right |
| limit |
Integer
|
3
|
Maximum number of visible toasts |
| duration |
Integer
|
4000
|
Default auto-dismiss duration in milliseconds. Use 0 to disable auto-dismiss |
| id |
String
|
shadcn-sonner-viewport
|
Viewport id used by Turbo Stream append actions |
| viewport_class_name |
String
|
nil
|
Additional classes for the fixed viewport |
| persistent |
Boolean
|
true
|
Adds data-turbo-permanent to the controller root |
toast(input, options)
| Prop | Type | Default | Description |
|---|---|---|---|
| id |
String, Number
|
generated
|
Reuse an id to update an existing toast |
| title |
String
|
nil
|
Toast title. Passing a string as the first argument sets this value |
| description |
String
|
nil
|
Toast body text |
| variant |
String
|
default
|
default, success, destructive, warning, or info |
| duration |
Number
|
toaster duration
|
Per-toast auto-dismiss duration in milliseconds |
| action |
String, Object
|
nil
|
Action label or { label, onClick } object |
| position |
String
|
toaster position
|
Routes the toast to a toaster with the matching position when present |
Accessibility
- The viewport uses
role="region"and an accessible label. - Each toast uses
role="status"andaria-live="polite". - Auto-dismiss pauses while the user hovers over a toast.
- Toasts can be dismissed with the close button or by swiping horizontally.
Top Center
<%= render Shadcn::Sonner.new(id: "sonner-top-center-viewport", position: :top_center, duration: 5000) do %>
<%= button_tag "Publish post", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Post published", description: "The changelog is live and subscribers were notified.", variant: "info" } %>
<% end %>
With Limit
<%= render Shadcn::SonnerComponent.new(id: "sonner-limit-viewport", limit: 2, duration: 8000) do %>
<div class="flex flex-wrap gap-2">
<%= button_tag "Create draft", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Draft created" } %>
<%= button_tag "Save edits", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Proposal saved", variant: "success" } %>
<%= button_tag "Show error", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Sync failed", variant: "destructive" } %>
</div>
<% end %>
No Auto Dismiss
<%= render Shadcn::Toaster.new(id: "sonner-persistent-viewport", duration: 0) do %>
<%= button_tag "Save profile", type: "button", data: { action: "click->shadcn--sonner#demo", title: "Profile saved", description: "Avatar, display name, and notification settings were saved.", variant: "success", duration: 0 } %>
<% end %>
On This Page