Skip to content

fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1)#3449

Open
bilal-karim wants to merge 2 commits into
mainfrom
a11y/1.4.1-toast-severity-icon
Open

fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1)#3449
bilal-karim wants to merge 2 commits into
mainfrom
a11y/1.4.1-toast-severity-icon

Conversation

@bilal-karim
Copy link
Copy Markdown
Member

@bilal-karim bilal-karim commented May 25, 2026

Summary

Toast variants (success/error/info/warning) previously distinguished severity exclusively by background color. Sighted users with color-vision differences (deuteranopia, protanopia, achromatopsia) could not distinguish a success toast from an error toast on the surface itself — only bg-success versus bg-danger.

This PR adds a leading Icon keyed to the variant via a variantIcon lookup. The neutral primary variant (no severity intent) renders no icon. Icon names mirror the established alert.svelte pattern.

+  const variantIcon: Readonly<Record<ToastVariant, IconName | null>> = {
+    primary: null,
+    success: 'success',
+    error: 'error',
+    info: 'info',
+    warning: 'warning',
+  };
...
+  {#if variantIcon[variant]}
+    <Icon name={variantIcon[variant]} class="shrink-0" />
+  {/if}
   <p class="text-sm">
     <slot />
   </p>

Before / After

Before — color-only severity After — color + icon severity
image image

success, error, info, warning each gain a leading severity icon. The neutral primary variant renders no icon (no severity intent). Background colors and message text are unchanged.

The accessible announcement is unchanged — Toaster's role="log" still carries the message text via the slot. The new icon is purely decorative.

Audit context

  • WCAG 2.2 SC 1.4.1 Use of Color (Level A) — High remediation priority.
  • Toasts are the product's primary confirmation channel for write operations (cancel, terminate, reset, signal, pause, batch action, deployment edit). Misreading a failure as a success here degrades operational trust.
  • Issue file: audit-output/issues/1.4.1-toast-severity-icon.md (part of the Wave 2 Holocene severity-primitive bundle — Toast is the first of four; Badge / Chip / Banner ship as separate PRs).

Affected callsites (all benefit automatically)

Every toaster.push({ variant, message }) consumer inherits the fix. No per-consumer change required. Includes activity-options-update, activity reset/cancel/terminate confirmations, all workflow client-actions (batch cancel/terminate/reset, pause/unpause, signal, update), event-history import, deployment edits, and file upload.

Test plan

  • Storybook: visual diff in toaster.stories.svelte — all 5 variants render correctly; primary shows no icon.
  • Screen reader smoke test (VoiceOver/NVDA): the message text continues to announce via role="log"; the decorative icon does not double-announce.
  • Trigger a cancel-confirmation flow (success toast) and a terminate-failure flow (error toast); confirm the icons render and are visually distinguishable.
  • Color-blind simulator (deuteranopia, protanopia, achromatopsia): the variant icon shape is perceptible against each variant background.
  • axe-core: no new violations.

Out of scope

  • Upgrading role="log"role="alert" for error toasts (that's SC 4.1.3 Status Messages).
  • Migrating to a title-plus-body structured layout.
  • Toast deduplication, queue limits, animation tuning.

🤖 Generated with Claude Code

….4.1)

Toast variants (success/error/info/warning) previously
distinguished severity exclusively by background color. Sighted
users with color-vision differences (deuteranopia, protanopia,
achromatopsia) could not tell a success toast from an error
toast on the surface itself.

Add a leading Icon keyed to the variant: success/error/info/
warning each render their named icon; the neutral 'primary'
variant (no severity) renders no icon.

Icon names match the existing alert.svelte pattern and resolve
to confirmed entries in the IconName set. The accessible
announcement is unchanged -- Toaster's role="log" still carries
the message text via the slot, and the new icon is purely
decorative.

Affects every toaster.push consumer including cancel/terminate/
reset/signal/pause confirmations, batch actions, deployment
edits, and file upload flows. Single primitive-level change;
no consumer migration required.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bilal-karim bilal-karim requested a review from a team as a code owner May 25, 2026 16:30
@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment May 25, 2026 4:42pm

Request Review

temporal-cicd flagged a TS warning on the conditional Icon render:
the expression variantIcon[variant] is typed IconName|null but
Icon's name prop expects IconName. Svelte template doesn't narrow
the type through the {#if} guard.

Extract the lookup into a reactive `$: icon = variantIcon[variant]`
in the script so the {#if icon} guard narrows icon to IconName
(non-null) for the Icon prop. Pure type refactor -- runtime
behavior identical.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant