Hacktron Design System

Toast

A brief notification that appears in the corner and dismisses itself.

const toast = useAppToast()toast.add({  title: 'Your changes have been saved',})

Props

Name Type Default
titlestring-
descriptionstring-
variant'success' | 'warning' | 'error' | 'default''default'
iconstring- (no icon shown)
actions{ label, variant?: ButtonVariant, onClick, ... }[]-
closeboolean | objectfalse

Guidelines

Do

  • Call useAppToast().add({ title, variant }) for feedback about something the user just did (saved, signed in, sent a code) - every real usage in apps/frontend's auth flows follows this shape.
  • Leave icon unset for the common case - most toasts in the app are just title (+ optional description), no icon at all.
  • For a toast that needs duration: 0 or a stable id - a persistent toast with required actions, or one tied to a specific one-off event so a duplicate trigger updates it instead of stacking a second one - use Nuxt UI's raw useToast() directly, since useAppToast() doesn't expose either option. See app.vue's cookie-consent toast and account.vue's connected-account toasts.
  • Expect success/warning/error/default to render on the design system's own surface-semantic-*/surface-popover background with a matching stroke-semantic-*/stroke-strong ring (light pastel + light stroke in light mode, near-black + darker stroke in dark mode for the semantic ones) rather than Nuxt UI's vibrant bg-{color}/ring-{color} - full opacity, not a translucent tint, so it reads clearly regardless of what's behind the toast.
  • Expect at most 3 toasts visible at once, collapsed behind the front one (only expanding on hover) - matching shadcn's own Sonner stacking. A 4th toast fades the oldest out through the normal dismiss animation rather than cutting it off instantly.

Don't

  • Don't call Nuxt UI's raw useToast() directly unless you specifically need duration or id, which useAppToast() doesn't expose - otherwise use useAppToast() (packages/foundation/app/composables/useAppToast.ts) instead, the same way you'd reach for Alert/Button rather than UAlert/UButton directly.
  • Don't use a toast for something the user needs to act on before continuing (an unsaved-changes warning, a limit reached) - use Alert instead; a toast can be missed or auto-dismiss before it's read.
  • Don't restyle a toast per call site (:ui overrides, custom classes) - the shared look (rounded corners, shadow, background per state) is set once, globally, in app.config.ts's toast/toaster keys, since there's no per-instance component to own it locally.
  • Don't fire more than one toast for the same action - if a mutation can both succeed and fail, that's one toast with the outcome-appropriate variant, not two.
  • Don't rely on the toast's countdown being visible to the user - the progress bar is turned off globally (toaster.defaultVariants.progress: false), so duration still controls when it auto-dismisses, just without a visual cue.
  • Don't add a close button expecting it by default - Nuxt UI's own default shows one on every toast; this design system defaults close to false globally, so set it explicitly when a toast genuinely needs manual dismissal.

Examples

Warning

A risky-but-not-blocking outcome, like needing a second factor.

toast.add({  title: 'Second factor required',  variant: 'warning',})

Error

Feedback that an action failed.

toast.add({  title: 'Something went wrong',  variant: 'error',})

With icon

A deliberate glyph for a specific, recognizable event.

toast.add({  title: 'Password updated',  variant: 'success',  icon: 'i-lucide-lock',})

With actions

Auto-dismissing, but with a quick response action - variant here is this design system's own Button vocabulary, not Nuxt UI's raw color/variant.

toast.add({  title: 'Finding marked as resolved',  actions: [    { label: 'Undo', variant: 'Secondary', onClick: () => { /* ... */ } },    { label: 'Dismiss', variant: 'Ghost', onClick: () => { /* ... */ } },  ],})

Persistent with actions

A toast that stays until the user responds, for actions it can't auto-dismiss past.

const toast = useToast() // duration/id aren't available on useAppToast() - see its own filetoast.add({  title: 'We use cookies to improve your experience',  duration: 0,  close: true,  actions: [    { label: 'Accept', color: 'secondary', variant: 'solid', onClick: () => { /* ... */ } },    { label: 'Opt out', color: 'neutral', variant: 'ghost', onClick: () => { /* ... */ } },  ],})