Web Designguide12 min read

Success, Warning, Error: Semantic Status Colors That Work

Red and green alone fail WCAG 1.4.1. Build OKLCH status ramps with icons, text, and dark mode pairs that work globally.

The toast was green. The icon was a checkmark. The message said payment failed. Users trusted the color and missed the copy. Support logged forty tickets before anyone noticed engineering had reused success-surface on an error handler because red felt too aggressive for a billing page.

Semantic status colors—success, warning, error, and informational states—carry more responsibility than brand accents. They communicate outcome under stress, often at small sizes, often without users reading every word. Relying on hue alone violates WCAG 2.2 Success Criterion 1.4.1 Use of Color: color must not be the only visual means of conveying information, indicating an action, or distinguishing a visual element. This essay goes beyond red-green defaults: OKLCH status ramps, pairing icons and text, dark mode token pairs, cultural considerations, and token architecture that survives rebrands without breaking checkout, forms, and system alerts.

Status UI fails the same way form errors fail audits. A required field marked only with red asterisk color fails if the asterisk is not otherwise distinguished. Charts using only color to distinguish series fail without labels, patterns, or shapes. Green dot equals online with no label fails. Red border only on invalid input fails without error text and icon. Success toast with check and message passes because icon and text redundantly encode state. Color supports meaning; it cannot be the sole carrier. Icons and text are compliance infrastructure, not decoration.

Beyond complementary red and green

Red-green mapping is culturally loaded and physiologically uneven. Roughly eight percent of males and half a percent of females have some form of color vision deficiency; deuteranopia collapses red-green discrimination. Even typical vision struggles with red text on green background at small sizes. A robust status system uses distinct hues spaced around the wheel, lightness and chroma differences perceptible in grayscale simulation, iconography unique per state, explicit text visible or available to screen readers, and placement that puts errors near fields and success at completion points.

Suggested hue families in OKLCH are starting points tuned for your brand anchor and contrast targets. Success near teal-green, not pure green. Warning amber separates from error. Error red-orange diverges from brand coral if marketing already owns that hue. Info blue distinct from purple accent if accent is purple. Warning at yellow hues often needs dark text on fill; white text on warning background frequently fails contrast. Verify foreground-background pairs at fourteen and twelve pixel sizes in production CSS, not in Figma alone.

Keep status hues in dedicated sectors on the wheel and document ranges in your token README so rebrand exercises do not collapse success and marketing accent into one swatch. A practical starting map places success near one sixty-five degrees, warning near seventy-five to eighty-five, error near twenty to thirty-five, and info near two thirty to two sixty, each tuned after your brand anchor is known.

Single tokens do not scale across components. Build ramps like brand colors: bg or surface for banner and toast, border for outline emphasis, fg for text and icons on bg, emphasis for solid button fill, on-emphasis for text on solid button usually near white. Derive steps with consistent delta L in OKLCH so hover darkening perceptually matches across success, warning, and error, unlike HSL where yellow and red ramps look uneven at the same lightness numbers.

:root {
  --status-success-fg: oklch(0.45 0.12 165);
  --status-success-bg: oklch(0.95 0.04 165);
  --status-success-border: oklch(0.82 0.08 165);

  --status-warning-fg: oklch(0.45 0.14 75);
  --status-warning-bg: oklch(0.96 0.05 85);
  --status-warning-border: oklch(0.85 0.10 80);

  --status-error-fg: oklch(0.48 0.18 25);
  --status-error-bg: oklch(0.96 0.04 25);
  --status-error-border: oklch(0.84 0.10 25);
  --status-error-1: oklch(0.97 0.02 25);
  --status-error-6: oklch(0.75 0.12 25);
  --status-error-9: oklch(0.52 0.17 25);
  --status-error-11: oklch(0.42 0.16 25);
  --status-error-solid: var(--status-error-9);
  --status-error-on-solid: oklch(0.99 0 0);

  --status-info-fg: oklch(0.45 0.12 250);
  --status-info-bg: oklch(0.96 0.03 250);
  --status-info-border: oklch(0.85 0.08 250);
}

Name by role, not pigment. status-success-bg survives rebrand; green-100 breaks meaning when green becomes brand accent only. Destructive actions are status-related but not identical to error banners; may share ramp or use dedicated action-destructive-solid forked from error-nine.

Icons, text, and dark mode pairs

Every status component should expose icon, title, and message with role alert where appropriate. Do not rely on border-color alone for inline field errors. aria-invalid true carries semantics to assistive tech; visible text carries semantics to sighted users who cannot parse hue under sunlight on a phone.

Toast variants need the same contract as inline alerts. A slide-in banner without aria-live on errors fails both accessibility and comprehension when users look away during the animation. Pair role alert with assertive live region behavior on failure paths so screen readers interrupt politely with the same title sighted users see.

Icon choices should differ in shape, not only color: checkmark or circle-check for success, triangle-alert for warning, circle-x or octagon for error, circle-i for info. Filled versus outline weight should stay consistent across the set.

Enforce the icon map in component props so engineers cannot pass success icon with error copy even when tokens are wired correctly. Variant prop types should make mismatched icon and message a compile-time failure where your stack allows it.

Inline field errors still need visible error text below the input, not border hue alone. Toasts need titles that state the outcome in words users scan before they parse color.

<div class="alert alert--error" role="alert">
  <svg class="alert__icon" aria-hidden="true">...</svg>
  <div class="alert__body">
    <p class="alert__title">Payment failed</p>
    <p class="alert__message">Your card was declined. Try another method.</p>
  </div>
</div>

<style>
.alert--error {
  background: var(--status-error-bg);
  border: 1px solid var(--status-error-border);
  color: var(--status-error-fg);
}
.alert__icon {
  color: var(--status-error-fg);
  flex-shrink: 0;
}
</style>

Inverted light-mode status colors fail on dark surfaces: neon greens on pure black, warnings that glow like traffic lights, borders that disappear. Dark mode strategy lowers bg chroma, slightly raises bg lightness above page surface, raises fg lightness for text on dark bg, reduces solid button chroma to avoid OLED bloom, and keeps hue angles aligned with light mode for recognition.

Elevation logic parallels general dark surfaces described in Dark Mode Tokens That Fail Quietly; status banners are usually surface-plus-one emphasis, not floating at surface-zero. Test dark status tokens against both OLED black and common dark gray pages; border visibility differs. A warning that reads calm on #1a1a1a may shout on true black if chroma stayed too high.

Semantic status banners should not borrow marketing neon accents from the dark theme hero gradient. Users interpret status colors as system truth, not campaign energy. When in doubt, lower chroma on dark status fills before you lower lightness.

[data-theme="dark"] {
  --status-success-bg: oklch(0.22 0.05 165);
  --status-success-border: oklch(0.35 0.08 165);
  --status-success-fg: oklch(0.82 0.10 165);

  --status-warning-bg: oklch(0.24 0.06 85);
  --status-warning-border: oklch(0.38 0.10 80);
  --status-warning-fg: oklch(0.88 0.12 85);

  --status-error-bg: oklch(0.22 0.06 25);
  --status-error-border: oklch(0.38 0.12 25);
  --status-error-fg: oklch(0.85 0.12 25);
}

@media (forced-colors: active) {
  .alert {
    border: 1px solid ButtonText;
  }
  .alert__icon {
    forced-color-adjust: auto;
  }
}

Color meaning is not universal. Red signals danger in many Western UIs but celebration in some East Asian contexts. Green suggests go in traffic-influenced UX but carries regional associations. Yellow warning aligns with road signage in Europe and North America; other regions may prefer orange. Pair text labels that translate. Avoid red-green-only stock charts for global finance; add patterns or signed labels. Healthcare and government interfaces often mandate explicit wording beyond WCAG. When brand palette already uses red as accent, error state must diverge in lightness, icon, and label, not share accent-primary with subscribe and fatal error.

WCAG 1.4.1 in production and forced colors

Understanding SC 1.4.1 through examples clarifies audits. Form errors highlighted only in red fail if no icon, text label, or pattern marks the field. Data table red and green profit cells fail without plus-minus or labels. Status patterns need the same redundancy.

Pattern Fails 1.4.1 alone? Fix
Green dot online, gray offline Yes without label Add text or distinct icon shape
Red border only on invalid input Yes Add error text and icon
Success toast with check and message No Icon and text redundant
Profit cells red and green only Yes Add symbols or headers

Windows Forced Colors mode remaps tokens. Status UI must remain distinguishable when system palette overrides backgrounds. Borders on alert shells and sensible forced-color-adjust on icons help. Test icons and text remain visible; do not depend on subtle pastels high-contrast themes flatten.

QA in practice means grayscale screenshots where each state is still nameable, color-blind simulations on the full alert set, and a temporary CSS filter that removes hue while leaving layout so you can see whether icon and text still carry the story. Status infrastructure fails audits when only hue changes between success and error.

Run contrast on smallest production font sizes, not design defaults. Run dark mode side-by-side with light for hue family consistency. Walk screen reader paths with role alert and meaningful titles on failure handlers, not only on marketing demo pages.

Brand accent-primary and status colors serve different jobs. Accents attract clicks; status colors report system truth. When brand anchor is saturated coral or electric purple, map status hues away from that angle on the OKLCH wheel. If product insists on on-brand errors, compromise with shape and copy, not by reusing accent hex on fatal banners. Destructive buttons may share geometry with primary buttons while keeping error-solid fill so users recognize the family without confusing subscribe with payment failed.

Case study: The green toast that failed forty customers

A subscription billing SaaS shipped a redesigned checkout in May with softer visual language requested by marketing. Product asked to reduce aggressive red on payment errors because prior A/B test suggested red increased churn at the moment of card decline. Engineering reused success-surface and success-border tokens on a shared Toast component for all completion events, wiring payment_failed handler to the same variant as payment_succeeded with only message string differing. QA checklist covered happy path and one manual error screenshot with generic network failure copy on staging, not the production handler wiring matrix.

Launch week support queue filled with customers who believed payment succeeded because the toast was green and carried a checkmark icon from the success variant. Forty tickets referenced trust in color before reading text. Analytics showed sixty-two percent of users who received payment_failed toast did not click retry within five minutes, double baseline from the older red error banner. Accessibility audit commissioned after the incident noted WCAG 1.4.1 failure: color and icon shape encoded success while text encoded failure.

Remediation split status tokens from marketing accent tokens in the design system repo. error-solid, error-bg, error-fg ramp forked from brand coral by shifting hue toward red-orange twenty-five degrees and darkening fg for contrast on bg. success retained teal-green family one sixty-five. warning amber for card expiring soon. info for receipt emailed. Toast component required variant prop with enforced icon map: circle-x for error, check for success, triangle for warning, info circle for info. role alert and aria-live assertive on errors. Billing page removed success styling from any handler whose event name contained failed.

Dark mode pass lowered error bg chroma and raised border lightness after OLED test showed neon error glow on black. Forced colors test added border on toast shell. Finance dashboard table that colored cells red and green for variance received plus and minus symbols in cells and legend text not color alone. Deployment dashboard connection indicator added Online and Offline text beside dot.

Training slide for engineering listed anti-patterns: success green on error copy, toast only without aria-live, chart legends that are color swatches without labels, placeholder text in red implying invalid before interaction, using brand accent for info when marketing and system messages collide. Second QA pass ran grayscale on full toast set and color-blind simulations before redeploy. Component library added a enforced variant prop with TypeScript union type so payment_failed could not compile against success styling.

Finance dashboard variance cells gained plus and minus glyphs beside color. Deployment connection badge gained Online and Offline strings beside dot. Billing retry button copy was user-tested with error styling only, confirming that softer coral accent could remain on subscribe while error-solid carried decline truth without feeling punitive in user interviews.

Tickets dropped to baseline within two weeks of fix. Retry rate on declined cards improved. Marketing kept softer coral accent on subscribe CTAs without conflicting with error-solid on decline messaging. Leadership asked why design system had not separated status from accent earlier; answer was naming: components were organized by visual softness not semantic role.

The incident cost less than a full regulatory fine but more than the sprint that would have built status ramps upfront. The durable fix was treating status as a system within the system: OKLCH ramps, paired dark tokens, mandatory icon and text redundancy, and audits that remove color temporarily to see what remains.

Status colors are infrastructure. SC 1.4.1 is not a checkbox for forms alone; it applies to toasts, badges, connection indicators, and deployment dashboards. Build success, warning, and error so they still communicate when hue disappears, when users scan instead of read, and when brand wants everything to feel friendly except the moment the system must tell the truth.