PixelToast

Single toast notification card with title, message, tone, optional icon/action, loading spinner, and an auto-dismiss countdown bar — usually rendered by PxlKitToastProvider via useToast() (injectToast() in Angular).

  • Seven tones with matching border, text color, and HP-bar accent on pixel surface.
  • Auto-dismiss with a visual progress bar; hover, focus, a hidden page or a background window pause the countdown.
  • Announced by PxlKitToastProvider — assertively for red/gold by default, politely otherwise; overridable per toast.
  • Optional leading slot for icon, animatedIcon, or built-in loading spinner.
  • Action slot for inline retry / undo buttons; dismiss button always present.
Status
stable
Category
feedback
Since
v1.0.0

API

import { PixelToast } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
toast (required)ToastItem—The toast to show.
onDismiss (required)() => void—Called when the dismiss button is pressed, or the countdown runs out.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

The card is not a live region: PxlKitToastProvider announces each toast — its title and message — in the two live regions of its viewport, assertively (role="alert") for critical tones like red/gold or assertive toasts, politely (role="status") for the rest. A card rendered on its own is not announced. Hovering or focusing the card holds its auto-dismiss countdown, and so do a hidden page and a window in the background, to give everyone time to read it (WCAG 2.2.1). The dismiss button has aria-label="Dismiss notification" and a visible focus ring.

Keyboard

KeyActionWhen
TabMove focus into the toast (action button, then dismiss button).—
EnterActivate the focused action or dismiss button.Action or dismiss button focused.
SpaceActivate the focused action or dismiss button.Action or dismiss button focused.

Usage

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Default() {
  return <PixelToast toast={baseToast()} onDismiss={noop} />;
}

Examples

Default

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Default() {
  return <PixelToast toast={baseToast()} onDismiss={noop} />;
}

Tones

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Tones() {
  return (
    <div className="flex flex-col gap-3">
      <PixelToast
        toast={baseToast({ id: 't-neutral', tone: 'neutral', title: 'Heads up', message: 'Neutral notification.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-green', tone: 'green', title: 'Saved', message: 'Changes synced.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-cyan', tone: 'cyan', title: 'Info', message: 'Heads up — new build available.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-gold', tone: 'gold', title: 'Warning', message: 'Storage almost full.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-red', tone: 'red', title: 'Error', message: 'Upload failed.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-purple', tone: 'purple', title: 'Tip', message: 'Press ⌘K to search.' })}
        onDismiss={noop}
      />
      <PixelToast
        toast={baseToast({ id: 't-pink', tone: 'pink', title: 'Unlocked', message: 'You earned a badge.' })}
        onDismiss={noop}
      />
    </div>
  );
}

Surfaces

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Surfaces() {
  return (
    <div className="flex flex-col gap-3">
      <PixelToast
        surface="linear"
        toast={baseToast({ id: 't-linear', tone: 'cyan', title: 'Linear surface', message: 'Rounded card.' })}
        onDismiss={noop}
      />
      <PixelToast
        surface="pixel"
        toast={baseToast({ id: 't-pixel', tone: 'cyan', title: 'Pixel surface', message: 'Chamfered + HP bar.' })}
        onDismiss={noop}
      />
    </div>
  );
}

Loading

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Loading() {
  return (
    <PixelToast
      toast={baseToast({
        id: 't-loading',
        tone: 'cyan',
        title: 'Uploading…',
        message: 'Hang tight while we sync your files.',
        loading: true,
      })}
      onDismiss={noop}
    />
  );
}

With Action

import { PixelToast, PixelButton } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function WithAction() {
  return (
    <PixelToast
      toast={baseToast({
        id: 't-action',
        tone: 'red',
        title: 'Connection lost',
        message: 'We could not reach the server.',
        action: (
          <PixelButton size="sm" tone="red" variant="outline">
            Retry
          </PixelButton>
        ),
      })}
      onDismiss={noop}
    />
  );
}

With Icon

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

const DotIcon = () => (
  <span
    aria-hidden
    style={{
      width: 10,
      height: 10,
      borderRadius: 9999,
      background: 'currentColor',
      display: 'inline-block',
    }}
  />
);

export function WithIcon() {
  return (
    <PixelToast
      toast={baseToast({
        id: 't-icon',
        tone: 'green',
        title: 'Deployed',
        message: 'Build #482 is live.',
        icon: <DotIcon />,
      })}
      onDismiss={noop}
    />
  );
}

Assertive

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function Assertive() {
  return (
    <PixelToast
      toast={baseToast({
        id: 't-assertive',
        tone: 'cyan',
        title: 'Important',
        message: 'Forced assertive announcement.',
        assertive: true,
      })}
      onDismiss={noop}
    />
  );
}

Auto-dismiss Progress

import { PixelToast } from '@pxlkit/ui-kit';
import type { ToastItem } from '@pxlkit/ui-kit';

const noop = () => {};

const baseToast = (overrides: Partial<ToastItem> = {}): ToastItem => ({
  id: 'demo',
  title: 'Saved',
  message: 'Your changes have been persisted.',
  tone: 'cyan',
  duration: 0,
  ...overrides,
});

export function WithProgress() {
  return (
    <PixelToast
      toast={baseToast({
        id: 't-progress',
        tone: 'green',
        title: 'Auto-dismiss',
        message: 'Hover to pause the countdown bar.',
        duration: 4500,
      })}
      onDismiss={noop}
    />
  );
}

Related