bx

Sonner

An opinionated toast component for PRISM.

tsx
<div className="flex items-center gap-3">
  <Button
    variant="outline"
    onclick="document.dispatchEvent(new CustomEvent('bext-toast',{detail:{title:'Saved',description:'Your changes are stored.'}}))"
  >
    Show toast
  </Button>
  <Toaster position="bottom-right" />
</div>

Sonner is the toast system. It has a single Toaster component, mounted once in the layout: an empty fixed container. Toasts are then triggered from any script with document.dispatchEvent(new CustomEvent("bext-toast", { detail })); the island builds the toast, stacks it in the Toaster and removes it after the duration. The event must be dispatched on document, not on window.

The event detail accepts title, description, variant ("default", "success" or "destructive") and duration in milliseconds (4000 by default). Each toast has a close button. This port has no swipe-to-dismiss and no promise or loading state.

Installation

Terminal
$ bext ui add sonner

Usage

tsx
import { Toaster } from "@bext-stack/ui/primitives"

// In _layout.tsx (once):
<Toaster />

// Then anywhere in a client script:
document.dispatchEvent(new CustomEvent("bext-toast", {
  detail: { title: "Saved", description: "Your changes are stored.", variant: "success" }
}))

Anatomy

<Toaster position="bottom-right" />

API Reference

Toaster

Prop Type Default Description
position "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" "bottom-right" Corner or edge of the viewport where the toasts stack.
ariaLabel string "Notifications" Accessible name of the region landmark.
closeLabel string "Close" Accessible name of the close button of each toast. Written to data-close-label and read by the island.
className string — Extra classes on the fixed container.

bext-toast event detail

Prop Type Default Description
title string — First line, in semibold.
description string — Second line.
variant "default" | "success" | "destructive" "default" Neutral, green-tinted or destructive colors. "destructive" is announced as an alert, the others politely.
duration number 4000 Milliseconds before the toast is removed.

Data Attributes

Attributes you can target in custom CSS to override the style for each state.

Attribute On Values Description
data-ui="toaster" Toaster root — The island appends toasts to the first element with this attribute.
data-position Toaster root "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" Reflects the position prop.
data-close-label Toaster root string Reflects the closeLabel prop. The island uses it as the aria-label of each close button.
data-ui="toast-close" close button of each toast — Clicking removes the toast.

Keyboard

Key Action
Tab Reaches the close button of each visible toast.
Enter / Space On a close button: removes that toast.

Accessibility

The Toaster is a landmark and a live region: role="region", aria-label ("Notifications" by default, ariaLabel to change it) and aria-live="polite". Each toast is inserted with role="status", so screen readers announce it when they are idle. A toast with variant: "destructive" gets role="alert" and aria-live="assertive" and interrupts.

The close button of each toast is named "Close"; pass closeLabel to the Toaster to translate it. Toasts disappear on a timer and cannot be paused, and the focus is not moved to them. Do not use a toast as the only feedback for an important result: also update the page (inline message, focus move).

Try it live
Open in play

Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.

src/app/page.tsx 2 files · signals · runs in your browser
Pure PRISM + real bext signals: fine-grained, no re-render.