UI alerts (toasts)
View on GitHubUI alerts (toasts)
The small, transient toast in the top-right corner of the screen — a
one-line confirmation or error, auto-dismissing after a few seconds —
is a completely different system from
notifications: it's client-side only,
ephemeral, never persisted, and has nothing to do with
NotificationType/NotificationSetting. This category documents how
it works and the two independent ways to trigger one.
Guides, in the order you'd actually need them
- Trigger an alert from a backend action
— the zero-frontend-code way: flash a specific key on a redirect
response and it shows up as a toast automatically. Covers the one
easy mistake: the flash key vocabulary here is not the same as
the
Notificationmodel's severity vocabulary. - Trigger an alert from the frontend
— calling
addAlert()directly for actions that never round-trip through a redirect (a clipboard copy, an optimistic toggle). Worked example: adding the confirmation toast thatIssuePageHeader's "copy issue link" button doesn't have today. - Add a new alert type — worked
example adding a fifth
AlertType,neutral, for a muted toast that shouldn't compete visually with success/error/warning/info. - Customize alert behavior — worked example capping the visible stack at 5 (and a dedup-by-message variant), and where any future stacking/priority rule belongs.
- Testing components that use alerts
— the two different test shapes this codebase uses:
renderHookagainstuseAlert()directly with Inertia/AlertContainermocked out (forAlertContextbehavior itself), vs. a realAlertProviderwrapped around the component under test (for anything that merely consumes it).
The architecture in one paragraph
AlertProvider (mounted once, near the root of app.tsx) owns the
whole system: an in-memory list of AlertItems, rendered by
AlertContainer/Alert as a fixed top-right stack with a
framer-motion enter/exit animation, each auto-removed after a
duration (default 4000ms; pass 0 to keep it until the user
dismisses it manually). There are exactly two ways an alert gets
added to that list. Automatically: AlertProvider watches every
Inertia page load (an effect for the very first, server-rendered one)
and every subsequent visit (router.on('success', ...), chosen over a
usePage() effect specifically because Inertia reuses the same
flash object reference across visits with identical content, which
would silently skip a second identical flash under a naive effect) and
turns four specific flash keys — success, error, warning,
information — plus an optional sibling action_url key into a call
to addAlert(). Manually: any component can call useAlert().addAlert(message, type, duration, actionUrl)
directly, with no backend round-trip involved at all — see guide 2 for
when that's the right call.
