Sonner

An opinionated toast manager for Rails apps.

Recommended Stimulus Controller

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" and aria-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

              sonner/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

                sonner/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

                  sonner/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 %>