Photon Design System

Notification

A standalone status card — a tinted glyph, a title and one supporting line, an optional confirm action, and an optional close button. It owns its surface but nothing about timing or stacking: use it on its own, or drop it inside a Toast when you want it to appear, stack, and dismiss on its own. For a choice the user must make, use Dialog.

Import

import { Notification } from "@photon-ai/pho-ui/components/notification";

Colors

color is a closed, semantic set — it selects the glyph and its tint together. The brand card below also shows the reduced action row: a confirm link paired with a dismiss link.

New workspace invite

Olivia added you to the Photon team.

Draft saved

Your changes are stored locally.

Deployment ready

photon-web is live in production.

Usage nearing limit

You've used 90% of this month's build minutes.

Deploy failed

The build step exited with code 1.

<Notification
  color="success"
  title="Deployment ready"
  description="photon-web is live in production."
  onClose={() => {}}
/>
colorUse it for
brandInformational / product update (default)
grayNeutral, low-stakes note
successAn action completed
warningNeeds attention, not yet failed
errorSomething went wrong

Actions and dismissal

Two independent, optional pieces. Passing confirmLabel (with onConfirm) renders the action row: a brand confirm link beside a dismiss link whose text is dismissLabel (default "Dismiss"). Passing onClose renders the top-right close button — and wires the dismiss link, when the row is present, to the same handler. A card with neither is a read-only note.

Deploy failed

The build step exited with code 1.

Deployment ready

photon-web is live in production.

Draft saved

Your changes are stored locally.

// Full row + close button
<Notification
  color="error"
  title="Deploy failed"
  description="The build step exited with code 1."
  confirmLabel="Retry"
  onConfirm={retry}
  onClose={dismiss}
/>

// Close button only
<Notification title="Deployment ready" onClose={dismiss} />

// Read-only note — no actions
<Notification title="Draft saved" description="Stored locally." />

With Toast

The card is not a lifecycle. For a transient, auto-stacking notification, render it as the child of a Toast.Root whose own surface you neutralize (className="bg-transparent shadow-none ring-0") — the Toast manager owns motion, stacking, and swipe-to-dismiss, while the Notification owns the look. Wire the card’s onClose back to manager.close(toast.id).

function ToastList() {
  const manager = Toast.useToastManager();
  return manager.toasts.map((toast) => (
    <Toast.Root
      key={toast.id}
      toast={toast}
      className="bg-transparent shadow-none ring-0"
    >
      <Notification
        color={toast.data?.color ?? "brand"}
        title={toast.title}
        description={toast.description}
        onClose={() => manager.close(toast.id)}
      />
    </Toast.Root>
  ));
}

// Fire one:
manager.add({
  title: "Deployment ready",
  description: "photon-web is live in production.",
  data: { color: "success" },
});

Anatomy

  • Glyph + tint — chosen together by color; override just the glyph with icon
  • title / description — a semibold line and one supporting line (ReactNode)
  • Action row — appears only with confirmLabel: a brand confirm link beside a dismissLabel link (default "Dismiss"), wired to onConfirm / onClose
  • Close button — top-right; rendered only when you pass onClose

Props

Notification is a single element; extra HTMLAttributes<HTMLDivElement> (such as role, aria-live, id) spread onto its root <div>.

  • title — ReactNode — the semibold heading line (required)
  • description — ReactNode — one supporting line under the title
  • color — brand · gray · success · warning · error (default brand) — selects the glyph and its tint together
  • icon — Icon (Tabler) — override the status glyph while keeping the color’s tint
  • confirmLabel — string — confirm link text; its presence renders the action row
  • onConfirm — () => void — called when the confirm link is clicked
  • dismissLabel — string (default "Dismiss") — dismiss link text in the action row
  • onClose — () => void — called on dismiss; its presence renders the top-right close button
  • className — string — merged onto the card surface
  • ref — Ref<HTMLDivElement> — forwarded to the root <div>

Accessibility

  • Semantics — the card renders a plain <div>; it does not announce itself. Meaning comes from the title/description text, never color alone — the status glyph is aria-hidden. When a standalone card must be announced, pass role="status" (or role="alert" for error), which spreads onto the root. Fired through a Toast, the toast viewport already provides the live region.
  • Controls — the confirm and dismiss actions are real <button>s with visible text. The icon-only close button carries aria-label="Dismiss" and hides its X glyph from assistive tech.
  • Focus — the confirm/dismiss links and the close button each show a focus-visible ring; nothing is auto-focused when the card mounts.

Keyboard:

  • Tab — move through the confirm link, the dismiss link, then the close button (in that DOM order)
  • Enter / Space — activate the focused control

There is no built-in Esc handler — the card is not a dialog, so dismissal is always an explicit control (or the toast’s swipe).

Best practices

  • Say what happened, not what to decide — a title plus one line. Anything that needs a real decision belongs in a Dialog, not here.
  • Keep the action row to the one thing worth doing right now (Undo, View, Retry). It is a link pair, not a button bar.
  • Match color to meaning: error and warning for problems, success for confirmations, brand/gray for neutral information.
  • Don’t lean on color alone — the title has to carry the message for anyone who can’t see the tint.
  • When firing as a toast, don’t auto-dismiss error cards too fast — let people read and dismiss them.