oriUI

Toast

An imperative notification system. Push a toast from anywhere with useToast() and render <OriToaster /> once near the app root to display the queue. No event bus, no Vuex, no provide/inject — just a module-level reactive queue that every call site shares.

The live demo below fires real toasts into the docs page (an <OriToaster /> is already mounted in the docs layout). The appearance examples further down use the static <OriToast> component to show the look without triggering a notification.

Classes

ClassTypeDescription
ori-toasterBlockFixed portal container (pointer-events: none so it never blocks the page). Rendered by OriToaster via Teleport to body.
ori-toaster_top-rightPositionAnchors the stack to the top-right corner (default).
ori-toaster_top-leftPositionAnchors the stack to the top-left corner.
ori-toaster_top-centerPositionAnchors the stack to the top-center.
ori-toaster_bottom-rightPositionAnchors the stack to the bottom-right corner.
ori-toaster_bottom-leftPositionAnchors the stack to the bottom-left corner.
ori-toaster_bottom-centerPositionAnchors the stack to the bottom-center.
ori-toastBlockSingle notification card: surface background, role-coloured left-border accent, elevation shadow.
ori-color_*ColorRepoints --ori-color to drive the accent. Applied by useToast() severity shortcuts (success / danger / warn / info). Plain toast has no color class.
ori-toast__iconPartLeading icon element; coloured by --ori-color.
ori-toast__bodyPartFlex column holding the title and text.
ori-toast__titlePartBold heading above the body text.
ori-toast__textPartBody message; slightly muted opacity.
ori-toast__closePartDismiss button (aria-label=Dismiss notification); shown when closable is true.
role=alertStateApplied when color=danger (assertive live region). All other colors use role=status (polite).

Live demo

Click a button to fire a real toast. The queue is shared: rapid-clicking stacks multiple notifications; each auto-dismisses after 4 seconds unless sticky.

vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { success, error, warn, info, toast } = useToast();

const checkIcon = 'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z';
</script>

<template>
    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem">
        <OriButton
            variant="outline"
            color="success"
            text="Success"
            @click="success({ title: 'Saved', text: 'Your changes were saved.', icon: checkIcon })"
        />
        <OriButton
            variant="outline"
            color="danger"
            text="Error"
            @click="error({ title: 'Upload failed', text: 'The file could not be uploaded.' })"
        />
        <OriButton variant="outline" color="warn" text="Warning" @click="warn('Your session expires in 5 minutes.')" />
        <OriButton variant="outline" color="info" text="Info" @click="info('A new version is available.')" />
        <OriButton variant="outline" text="Plain" @click="toast('Just a plain notification.')" />
        <OriButton
            variant="outline"
            text="Sticky"
            @click="toast({ text: 'I stay until you dismiss me.', duration: 0, closable: true })"
        />
    </div>
</template>

Severity / colors

Four severity shortcuts preset a color; the plain toast() call has no color class (neutral surface accent).

Your changes have been saved.
Your session expires in 5 minutes.
A new version is available.
Just a plain notification.
html
<!-- Static appearance — no auto-dismiss or queue without JS. -->
<div class="ori-toast ori-color_success" role="status">
    <div class="ori-toast__body">
        <div class="ori-toast__text">Your changes have been saved.</div>
    </div>
</div>
<div class="ori-toast ori-color_danger" role="alert">
    <div class="ori-toast__body">
        <div class="ori-toast__text">Upload failed. Please try again.</div>
    </div>
</div>
<!-- plain: no ori-color_* class -->
<div class="ori-toast" role="status">
    <div class="ori-toast__body">
        <div class="ori-toast__text">Just a plain notification.</div>
    </div>
</div>
vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { success, error, warn, info, toast } = useToast();
</script>

<template>
    <!-- each call returns the toast id -->
    <OriButton text="Success" @click="success('Your changes have been saved.')" />
    <OriButton text="Error" @click="error('Upload failed. Please try again.')" />
    <OriButton text="Warning" @click="warn('Your session expires in 5 minutes.')" />
    <OriButton text="Info" @click="info('A new version is available.')" />
    <OriButton text="Plain" @click="toast('Just a plain notification.')" />
</template>

Titles and icons

Pass title for a bold heading and icon (SVG path) for a coloured leading icon.

Saved
Your changes were saved successfully.
Heads up
A new version is available.
html
<div class="ori-toast ori-color_success" role="status">
    <i class="ori-icon ori-toast__icon" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg>
    </i>
    <div class="ori-toast__body">
        <div class="ori-toast__title">Saved</div>
        <div class="ori-toast__text">Your changes were saved successfully.</div>
    </div>
</div>
vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { success, error, info } = useToast();

const checkIcon = 'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z';
</script>

<template>
    <OriButton
        text="Fire"
        @click="success({ title: 'Saved', text: 'Your changes were saved successfully.', icon: checkIcon })"
    />
    <OriButton text="Error" @click="error({ title: 'Upload failed', text: 'The file could not be uploaded.' })" />
    <OriButton text="Info" @click="info({ title: 'Heads up', text: 'A new version is available.' })" />
</template>

Closable

closable: true (the default when called via useToast()) renders a dismiss button. Pass closable: false to suppress it — useful for short-lived toasts that auto-dismiss quickly.

Dismiss me whenever you like.
I auto-dismiss — no button needed.
html
<!-- with dismiss button -->
<div class="ori-toast ori-color_info" role="status">
    <div class="ori-toast__body">
        <div class="ori-toast__text">Dismiss me whenever you like.</div>
    </div>
    <button type="button" class="ori-toast__close" aria-label="Dismiss notification">×</button>
</div>
vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { info } = useToast();
</script>

<template>
    <!-- closable: true is the default from useToast() -->
    <OriButton text="Closable" @click="info({ text: 'Dismiss me.', closable: true })" />
    <!-- suppress the button for quick notifications -->
    <OriButton text="No button" @click="info({ text: 'Gone in 4 s.', closable: false })" />
</template>

Duration and sticky

The default auto-dismiss delay is 4000 ms. Pass duration: 0 to make a toast sticky — it stays until the user clicks the dismiss button or you call dismiss(id) / clear() manually.

I auto-dismiss after 4 s (default duration).
I stay until dismissed (duration: 0).
vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { toast, warn } = useToast();
</script>

<template>
    <!-- default: auto-dismiss after 4000 ms -->
    <OriButton text="Default" @click="toast('Gone in 4 s.')" />

    <!-- custom duration -->
    <OriButton text="8 s" @click="toast({ text: 'Gone in 8 s.', duration: 8000 })" />

    <!-- sticky: pass duration: 0 -->
    <OriButton text="Sticky" @click="warn({ text: 'I stay until dismissed.', duration: 0, closable: true })" />
</template>

Positions

Place <OriToaster> once (typically in the app root layout) and set its position prop. The six positions are anchored with CSS — no re-ordering of the queue required. Bottom stacks grow upward (flex-direction: column-reverse).

vue
<!-- top-right is the default -->
<OriToaster position="top-right" />

<!-- other positions -->
<OriToaster position="top-left" />
<OriToaster position="top-center" />
<OriToaster position="bottom-left" />
<OriToaster position="bottom-right" />
<OriToaster position="bottom-center" />

Programmatic dismiss

toast() and the severity shortcuts all return the toast id. Pass it to dismiss(id) to remove a specific toast early, or call clear() to flush the entire queue.

vue
<script setup lang="ts">
import { useToast } from '@oriui/vue';

const { toast, dismiss, clear } = useToast();

async function saveAndConfirm() {
    const id = toast({ text: 'Saving…', duration: 0 });
    await save();
    dismiss(id);
    toast({ text: 'Saved!', color: 'success' });
}
</script>

Accessibility

Toast follows the ARIA live-region pattern — content is announced without moving keyboard focus.

  • role="alert" (assertive) — used when color="danger". The announcement interrupts the screen reader immediately. Reserve this for genuine errors.
  • role="status" (polite) — used for every other color, including the plain/neutral toast. The announcement waits for the reader to finish its current utterance.
  • The dismiss button carries aria-label="Dismiss notification" so its purpose is clear with no visible text.
  • Toasts are non-modal: they never trap focus and do not require keyboard interaction to proceed. They appear and disappear without a focus change.
  • Content must be present when the element is inserted — some screen readers announce only the content that is in the live region at the moment it is added to the DOM. useToast() pushes a fully-constructed item, so the rendered <OriToast> already contains its text on mount.
  • Set duration: 0 and closable: true for toasts that contain interactive content or links, so keyboard users can reach them before auto-dismiss removes the element.
  • The <OriToaster> uses a <TransitionGroup> with prefers-reduced-motion support — the slide animation is disabled for users who opt out; only the fade remains.
KeyAction
TabMoves focus to the dismiss button (if shown).
EnterActivates the focused dismiss button.
SpaceActivates the focused dismiss button.

Framework API

useToast()

The composable is a module-level singleton — every call returns the same reactive queue. Import it anywhere; no Vue injection or plugin registration required. Its behaviour is the framework-agnostic useToast — the same imperative API ships from @oriui/headless/vue and @oriui/headless/svelte; this @oriui/vue re-export is unchanged.

ts
import { useToast } from '@oriui/vue';

const { toasts, toast, success, error, warn, info, dismiss, clear } = useToast();
Return valueType / signatureDescription
toastsToastItem[] (reactive)The live queue; rendered by <OriToaster>. Read-only — mutate via push/clear.
toast(options: ToastOptions | string) => numberPush a plain notification; returns the toast id.
success(options: ToastOptions | string) => numberPush with color="success" preset.
error(options: ToastOptions | string) => numberPush with color="danger" preset.
warn(options: ToastOptions | string) => numberPush with color="warn" preset.
info(options: ToastOptions | string) => numberPush with color="info" preset.
dismiss(id: number) => voidRemove a specific toast by its id and clear its timer.
clear() => voidRemove all toasts and clear all pending timers.

Passing a plain string is shorthand for { text: string }.

ToastOptions

OptionTypeDefaultDescription
closablebooleantrueRenders a dismiss button on the toast.
colorThemeColorSemantic color role; preset by severity shortcuts.
durationnumber4000Auto-dismiss delay in ms. 0 keeps the toast until dismissed.
iconstringSVG path for a leading icon.
textstringBody message.
titlestringBold heading above the body text.

ThemeColor: 'primary' | 'secondary' | 'success' | 'warn' | 'danger' | 'info' | 'surface' | 'background'

<OriToaster> props

Place this component once near the app root (e.g. in the main layout). It Teleports to <body> and is gated behind an onMounted check so SSR markup stays stable.

PropTypeDefaultDescription
position'top-left' | 'top-right' | 'top-center' | 'bottom-left' | 'bottom-right' | 'bottom-center''top-right'Screen corner for the stack.

OriToaster declares no custom events. It drives itself from the shared queue returned by useToast() and calls dismiss(id) internally when a toast emits close.

<OriToast> props

The single-toast card component. Used internally by <OriToaster> but also usable standalone to display a static notification embedded in a page.

PropTypeDefaultDescription
closablebooleanfalseRenders a dismiss button (aria-label="Dismiss notification").
colorThemeColor'surface'Semantic color role — drives the accent border and icon color.
iconstringSVG path for a leading icon; rendered with aria-hidden="true".
textstringBody message. Use the default slot for richer markup.
titlestringBold heading above the body text.

Events

EventPayloadDescription
closeEmitted when the dismiss button is clicked. Use it to call dismiss(id) or hide the toast.

OriToast does not set inheritAttrs: false, so extra attributes (class, style, data-*) fall through to the root <div role="status/alert">.

Slots

SlotDescription
defaultBody content. Replaces the text prop; renders inside .ori-toast__text.
iconLeading icon. Replaces the icon prop render; falls back to it when not provided.
titleTitle content. Replaces the title prop; renders inside .ori-toast__title.