Shadix.Components.Sonner (shadix v0.0.1)

Copy Markdown View Source

Toast notifications, the Shadix take on shadcn's sonner integration.

shadcn delegates entirely to the sonner npm library (a React component with its own imperative toast() API). There is no equivalent runtime in Phoenix, so this module is a Phoenix.LiveComponent that owns a :toasts stream and renders an accessible region: a visually-hidden polite region, a visually-hidden assertive region, and the visible stream <ol> of toast/1s.

Usage

Mount once in your layout (or root LiveView):

<.live_component module={Shadix.Components.Sonner} id="shadix-toaster" flash={@flash} />

Trigger a toast from a LiveView:

def handle_event("save", _p, socket) do
  Shadix.Components.Sonner.send_toast(title: "Saved", message: "Done", variant: "success")
  {:noreply, socket}
end

Existing flashes also appear automatically and are cleared so they won't replay:

{:noreply, put_flash(socket, :error, "Something went wrong")}

Variants

Supported variant values are default | success | info | warning | error. The error and warning variants are considered high-priority and use role="alertdialog" with an aria-live="assertive" announcement channel. All other variants use role="dialog" with an aria-live="polite" channel.

Accessibility

Each toast is role="dialog" or role="alertdialog", aria-modal="false", focusable (tabindex="0"), and links its title/description via aria-labelledby/aria-describedby. Two sr-only live regions (polite and assertive) announce new toasts to screen readers. The ShadixSonner JS hook handles: auto-dismiss after ~4 s, pause-on-hover/focus, Escape to dismiss the focused toast, and F6 to move focus to the toast stack. Entry/exit animation is pure-CSS via data-state; prefers-reduced-motion disables transitions.

Simplifications vs. sonner

This v1 deliberately scopes out a lot of the sonner feature set:

  • No swipe-to-dismiss, action buttons, promise toasts, programmatic update/dismiss-by-id, stacking-collapse, or position configuration (fixed bottom-right). The duration is a fixed ~4 s.

Summary

Functions

Builds a normalized toast map from user-supplied options. Pure; used by send_toast/2 and the flash bridge to feed the :toasts stream.

Sends a toast to a mounted Sonner live component from anywhere with access to the component (typically a LiveView handle_event/handle_info). opts are passed to build_toast/1 (:title, :message, :variant, optional :id).

A single toast notification. Rendered by the Sonner live component's stream loop, but kept as a standalone function component so its accessibility markup is testable in isolation.

Functions

build_toast(opts)

Builds a normalized toast map from user-supplied options. Pure; used by send_toast/2 and the flash bridge to feed the :toasts stream.

send_toast(toaster_id \\ "shadix-toaster", opts)

Sends a toast to a mounted Sonner live component from anywhere with access to the component (typically a LiveView handle_event/handle_info). opts are passed to build_toast/1 (:title, :message, :variant, optional :id).

toast(assigns)

A single toast notification. Rendered by the Sonner live component's stream loop, but kept as a standalone function component so its accessibility markup is testable in isolation.

:variant drives both styling and a11y priority: error/warning render as role="alertdialog" (high priority), the rest as role="dialog". The toast is focusable (tabindex="0") and links its title/description via aria-labelledby/aria-describedby only when those elements exist. Dismissal is delegated to the ShadixSonner hook (the close button carries no phx-click), and enter/exit is pure-CSS via data-state.

Attributes

  • id (:string) (required)
  • variant (:string) - Defaults to "default". Must be one of "default", "success", "info", "warning", or "error".
  • title (:string) - Defaults to nil.
  • message (:string) - Defaults to nil.
  • class (:string) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block