apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Toast
Variant↳ other

Feedback

Toast

Transient notification primitive with an imperative API.

Toast

Transient notifications driven by an imperative API. Mount <Toaster /> once at your app root, then call toast(…) from anywhere — components, hooks, Redux middleware, fetch interceptors, service workers — without provider plumbing.

Overview — info, success, warning, and error toasts

Loading preview…
Overview.tsx
tsx
// app/layout.tsx
import { Toaster } from 'apx-ds';

export default function Layout({ children }) {
  return (
    <>
      {children}
      <Toaster />
    </>
  );
}
// app/layout.tsx
import { Toaster } from 'apx-ds';

export default function Layout({ children }) {
  return (
    <>
      {children}
      <Toaster />
    </>
  );
}
ts
// anywhere
import { toast } from 'apx-ds';

toast('Saved.');
toast.success('Profile updated');
toast.error('Could not save. Please retry.');
// anywhere
import { toast } from 'apx-ds';

toast('Saved.');
toast.success('Profile updated');
toast.error('Could not save. Please retry.');

Why an imperative API

Notifications happen in code that is far away from React's view tree — the success / failure path of an async action, a global error handler, a third-party callback. Forcing every caller to thread a context through to dispatch a notification leads to prop drilling for an ephemeral concern. The Toaster's module-level store sidesteps that: callers reach for toast(…) like they would console.log(…), and the singleton Toaster handles the rest.

API

<Toaster />

PropTypeDefaultDescription
positionToastPosition'bottom-right'Where the stack anchors on screen.
maxnumber3Maximum simultaneously visible toasts.
gapnumber8Pixels between stacked toasts.
expandbooleanfalseAlways-expanded vs collapsed-with-hover-expand.
durationnumber5000Default auto-dismiss in ms. 0 is persistent.
closeButtonbooleanfalseShow the × button on every toast.
richColorsbooleanfalseDefault to soft (tinted) variant.
pauseOnHoverbooleantruePause auto-dismiss while pointer is over the region.
pauseOnFocusLossbooleantruePause auto-dismiss when tab visibility is hidden.
portalContainerHTMLElement | nulldocument.bodyOverride the portal target.
aria-labelstring'Notifications'Live region label.

toast(…) and aliases

ts
toast(title, opts?);            // intent: neutral
toast.success(title, opts?);
toast.error(title, opts?);
toast.warning(title, opts?);
toast.info(title, opts?);
toast.loading(title, opts?);

toast.promise(promise, {
  loading: ReactNode,
  success: ReactNode | ((data) => ReactNode),
  error: ReactNode | ((err) => ReactNode),
}, opts?);

toast.dismiss();        // dismiss everything
toast.dismiss(id);      // dismiss a specific toast

toast.update(id, {
  title?,
  description?,
  intent?,
  duration?,
  icon?,
  action?,
  cancel?,
  variant?,
});
toast(title, opts?);            // intent: neutral
toast.success(title, opts?);
toast.error(title, opts?);
toast.warning(title, opts?);
toast.info(title, opts?);
toast.loading(title, opts?);

toast.promise(promise, {
  loading: ReactNode,
  success: ReactNode | ((data) => ReactNode),
  error: ReactNode | ((err) => ReactNode),
}, opts?);

toast.dismiss();        // dismiss everything
toast.dismiss(id);      // dismiss a specific toast

toast.update(id, {
  title?,
  description?,
  intent?,
  duration?,
  icon?,
  action?,
  cancel?,
  variant?,
});

toast(…) returns the toast's id — pass it to toast.dismiss(id) / toast.update(id, …) to manage the toast later. Calling toast(…, { id }) with an existing id updates in place instead of stacking; this is how toast.promise cycles a single toast through loading → success / error.

ToastOptions

OptionTypeDescription
idstringDedup key. New calls with the same id update the existing toast.
descriptionReactNodeSecondary text rendered under the title.
iconReactNode | falseOverride the per-intent icon; false removes it.
action{label, onClick}Primary button (defaults to dismissing the toast on click).
cancel{label, onClick}Secondary button (defaults to dismissing the toast on click).
durationnumberAuto-dismiss in ms. 0 keeps the toast until manually dismissed.
dismissiblebooleanWhen false, hides the close button and disables swipe.
onDismiss(id) => voidFired when the toast leaves the queue via explicit dismiss.
onAutoClose(id) => voidFired when the toast leaves the queue via timer expiry.
variantToastVariantPer-toast variant override (otherwise inherits richColors setting).

Variants

VariantVisual
solidPaper background, neutral border. Default minimal style.
outlinePaper background, intent-colored border.
softIntent-tinted background + matching border. Default when richColors={true}.

Intents

IntentColor roleDefault icon
neutralneutralmessage bubble
successsuccesscheck-circle
errordangeralert-octagon
warningwarningalert-triangle
infoinfoinfo-circle
loadingneutralspinner (CSS animation)

error intent uses role="alert"; every other intent uses role="status".

Accessibility

  • The Toaster renders an <ol role="region" aria-live="polite" aria-label="Notifications">. The list is present even when empty so screen readers stay subscribed.
  • Each toast is <li role="status"> (role="alert" for the error intent).
  • aria-atomic="true" ensures the full toast re-announces when toast.update patches it.
  • Close / action / cancel buttons are native focusable buttons with explicit labels.
  • F8 focuses the toast region (platform convention). Arrow Up / Down cycles between visible toasts; Esc dismisses the focused toast.
  • pauseOnHover + pauseOnFocusLoss (both on by default) give users time to read.
  • Motion respects prefers-reduced-motion: opacity-only transition, no slide.
  • axe-core passes for every intent × variant combination.

Patterns

Optimistic update with undo

tsx
async function archive(emailId: string) {
  await api.archive(emailId);
  toast('Email archived', {
    description: 'Removed from inbox.',
    action: {
      label: 'Undo',
      onClick: () => api.unarchive(emailId).then(() => toast.success('Restored.')),
    },
  });
}
async function archive(emailId: string) {
  await api.archive(emailId);
  toast('Email archived', {
    description: 'Removed from inbox.',
    action: {
      label: 'Undo',
      onClick: () => api.unarchive(emailId).then(() => toast.success('Restored.')),
    },
  });
}

Async with promise

tsx
function onSubmit(values) {
  toast.promise(api.savePost(values), {
    loading: 'Saving post…',
    success: (post) => `Posted as “${post.title}”`,
    error: (err) => `Failed: ${err.message}`,
  });
}
function onSubmit(values) {
  toast.promise(api.savePost(values), {
    loading: 'Saving post…',
    success: (post) => `Posted as “${post.title}”`,
    error: (err) => `Failed: ${err.message}`,
  });
}

Outside React

ts
// axios interceptor
api.interceptors.response.use(undefined, (error) => {
  if (error.response?.status === 401) {
    toast.error('Your session expired. Please sign in again.');
  }
  return Promise.reject(error);
});
// axios interceptor
api.interceptors.response.use(undefined, (error) => {
  if (error.response?.status === 401) {
    toast.error('Your session expired. Please sign in again.');
  }
  return Promise.reject(error);
});

Anti-patterns

  • Toast inside a Modal. Toasts portal to document.body so they render correctly above modals, but the UX is confusing (two simultaneous attention surfaces). Prefer an inline Alert inside the Modal body.
  • Toast for blocking confirmation. Toasts are transient — they auto-dismiss. Use a Modal or AlertDialog when you need a synchronous yes/no answer.
  • Multiple Toasters. Mount exactly one Toaster per app. Multiple Toasters with different position props is allowed (rare), but they share the same queue.

Examples

Each example below mounts its own <Toaster /> so the preview is self-contained — in a real app you mount one Toaster at the app shell. Click any trigger button to fire a toast; the queue, animation, and ARIA live region all run live.

Basic

Loading preview…
Basic.tsx

Intents

Loading preview…
Intents.tsx

RichColors

Loading preview…
RichColors.tsx

WithAction

Loading preview…
WithAction.tsx

Promise

Loading preview…
Promise.tsx

Persistent

Loading preview…
Persistent.tsx

DismissAll

Loading preview…
DismissAll.tsx

Dedup

Loading preview…
Dedup.tsx

Positions

Loading preview…
Positions.tsx

ExpandedStack

Loading preview…
ExpandedStack.tsx

CustomIcon

Loading preview…
CustomIcon.tsx

Theming

tsx
defineTheme({
  components: {
    Toast: {
      styleOverrides: {
        region: '',
        content: 'shadow-2xl',
        action: 'font-semibold',
        close: '',
      },
    },
  },
});
defineTheme({
  components: {
    Toast: {
      styleOverrides: {
        region: '',
        content: 'shadow-2xl',
        action: 'font-semibold',
        close: '',
      },
    },
  },
});

Props

PropTypeDefaultDescription
closeButton*boolean—Whether the close `×` should render.
duration*number—Effective duration (Toaster-level default applied; `0` is persistent).
item*ToastItem—The toast item to render.
onAutoClose*(id: string) => void—Called when the toast's timer expires.
onDismiss*(id: string) => void—Called when the user explicitly dismisses (close button / action with dismissOnClick).
paused*boolean—Whether the toast's timer is currently paused (hover / focus-loss / promise pending).
position*enum—Anchor side — drives enter/exit slide direction.
variant*enum—Resolved variant (Toaster-level default applied).
classNamestring—Override className on the toast content.
styleCSSProperties—Override inline style on the toast content.
sxSx—Theme-aware inline style.