Barrierefreie React-Toast-Benachrichtigungen mit Claude Code bauen
React-Guide für Toasts mit Queue, Auto-Dismiss, Pause, aria-live, Reduced Motion und mobilen Safe Areas.
Toast-Benachrichtigungen wirken klein: “Gespeichert”, “Export gestartet”, “Zahlung fehlgeschlagen”. In echten Produkten beeinflussen sie aber Barrierefreiheit, Vertrauen, mobile Conversion und Support-Aufwand. Ein schlechter Toast verschwindet zu früh, nutzt überall alert, verdeckt den wichtigsten CTA oder ist der einzige Ort, an dem ein wichtiger Fehler steht.
Claude Code kann die sichtbare Komponente schnell schreiben. Für eine produktionsreife Umsetzung braucht der Prompt mehr: Queue, Auto-Dismiss, Pause bei Hover und Fokus, role="status" statt role="alert" für nicht dringende Hinweise, prefers-reduced-motion, mobile Safe Areas und Review-Fragen. Ergänzend passen Claude Code Accessibility, Animation Implementation, Responsive Design und React Development.
Designregeln
Ein Toast ist kein Modal. Er darf den Fokus nicht fangen und die Aufgabe nicht blockieren. Verwende ihn für kurze, nicht blockierende Statusmeldungen. Wenn Nutzer ein Feld korrigieren, eine riskante Aktion bestätigen oder eine Zahlung retten müssen, gehört die Information zusätzlich dauerhaft in die Seite.
Diese Umsetzung folgt klaren Regeln:
- maximal 3 sichtbare Toasts
success,infoundwarningmitrole="status"errornur bei dringenden Fehlern mitrole="alert"- automatisches Schließen, pausiert bei Hover und Fokus
- Schließen-Button pro Toast
- keine Animation bei
prefers-reduced-motion - mobile Abstände mit
env(safe-area-inset-*)
Offizielle Quellen: MDN status role, MDN alert role, W3C WCAG Status Messages, W3C WCAG Pause, Stop, Hide, MDN setTimeout, MDN prefers-reduced-motion und Claude Code Docs.
Kopierbarer React-Code
Lege ToastProvider.tsx an. Die Datei braucht nur React. In Next.js App Router gehört "use client"; an den Anfang.
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ReactNode,
} from "react";
type ToastTone = "success" | "info" | "warning" | "error";
type ToastInput = { title: string; description?: string; tone?: ToastTone; durationMs?: number };
type ToastItem = Required<Omit<ToastInput, "durationMs">> & { id: string; durationMs: number; createdAt: number };
type ToastContextValue = { showToast: (input: ToastInput) => string; dismissToast: (id: string) => void };
const ToastContext = createContext<ToastContextValue | null>(null);
const MAX_VISIBLE_TOASTS = 3;
const DEFAULT_DURATION = 5000;
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<ToastItem[]>([]);
const dismissToast = useCallback((id: string) => {
setToasts((current) => current.filter((toast) => toast.id !== id));
}, []);
const showToast = useCallback((input: ToastInput) => {
const id = crypto.randomUUID();
const nextToast: ToastItem = {
id,
title: input.title,
description: input.description ?? "",
tone: input.tone ?? "info",
durationMs: input.durationMs ?? DEFAULT_DURATION,
createdAt: Date.now(),
};
setToasts((current) => [...current, nextToast].slice(-MAX_VISIBLE_TOASTS));
return id;
}, []);
const value = useMemo(() => ({ showToast, dismissToast }), [showToast, dismissToast]);
return (
<ToastContext.Provider value={value}>
{children}
<ToastViewport toasts={toasts} onDismiss={dismissToast} />
</ToastContext.Provider>
);
}
export function useToast() {
const context = useContext(ToastContext);
if (!context) throw new Error("useToast must be used inside ToastProvider");
return context;
}
function ToastViewport({ toasts, onDismiss }: { toasts: ToastItem[]; onDismiss: (id: string) => void }) {
return (
<div className="toast-viewport" aria-label="Benachrichtigungen">
{toasts.map((toast) => (
<ToastCard key={toast.id} toast={toast} onDismiss={onDismiss} />
))}
</div>
);
}
function ToastCard({ toast, onDismiss }: { toast: ToastItem; onDismiss: (id: string) => void }) {
const [paused, setPaused] = useState(false);
const remainingMs = useRef(toast.durationMs);
const startedAt = useRef<number | null>(null);
const timeoutId = useRef<number | null>(null);
useEffect(() => {
if (toast.durationMs <= 0 || paused) return;
startedAt.current = Date.now();
timeoutId.current = window.setTimeout(() => onDismiss(toast.id), remainingMs.current);
return () => {
if (timeoutId.current !== null) window.clearTimeout(timeoutId.current);
if (startedAt.current !== null) remainingMs.current -= Date.now() - startedAt.current;
};
}, [onDismiss, paused, toast.durationMs, toast.id]);
const role = toast.tone === "error" ? "alert" : "status";
return (
<section
className={`toast-card toast-card--${toast.tone}`}
role={role}
aria-atomic="true"
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
onFocus={() => setPaused(true)}
onBlur={() => setPaused(false)}
>
<div className="toast-card__content">
<strong className="toast-card__title">{toast.title}</strong>
{toast.description ? <p>{toast.description}</p> : null}
</div>
<button type="button" className="toast-card__close" aria-label={`${toast.title} schließen`} onClick={() => onDismiss(toast.id)}>
×
</button>
</section>
);
}
Ergänze toast.css.
.toast-viewport {
position: fixed;
top: max(16px, env(safe-area-inset-top));
right: max(16px, env(safe-area-inset-right));
z-index: 1000;
display: grid;
gap: 10px;
width: min(380px, calc(100vw - 32px));
pointer-events: none;
}
.toast-card {
pointer-events: auto;
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: 12px;
padding: 14px 14px 14px 16px;
border: 1px solid #d8dee8;
border-left-width: 5px;
border-radius: 8px;
background: #fff;
color: #172033;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.18);
animation: toast-slide-in 180ms ease-out;
}
.toast-card--success { border-left-color: #15803d; }
.toast-card--info { border-left-color: #2563eb; }
.toast-card--warning { border-left-color: #b45309; }
.toast-card--error { border-left-color: #b91c1c; }
.toast-card__title { display: block; font-size: 0.95rem; line-height: 1.35; }
.toast-card p { margin: 4px 0 0; color: #46536a; font-size: 0.875rem; line-height: 1.5; }
.toast-card__close {
min-width: 32px;
min-height: 32px;
border: 0;
border-radius: 6px;
background: transparent;
color: #526071;
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
}
.toast-card__close:hover,
.toast-card__close:focus-visible { background: #eef2f7; outline: 2px solid transparent; }
@keyframes toast-slide-in {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 640px) {
.toast-viewport { left: 16px; right: 16px; width: auto; }
}
@media (prefers-reduced-motion: reduce) {
.toast-card { animation: none; }
}
Verwendung:
import { ToastProvider, useToast } from "./ToastProvider";
import "./toast.css";
function SaveProfileButton() {
const { showToast } = useToast();
async function handleSave() {
try {
await new Promise((resolve) => window.setTimeout(resolve, 600));
showToast({
tone: "success",
title: "Profil gespeichert",
description: "Die Änderungen erscheinen beim nächsten Öffnen dieser Ansicht.",
});
} catch {
showToast({
tone: "error",
title: "Speichern fehlgeschlagen",
description: "Prüfe die Verbindung und versuche es erneut.",
durationMs: 8000,
});
}
}
return <button onClick={handleSave}>Speichern</button>;
}
export default function App() {
return (
<ToastProvider>
<main>
<h1>Einstellungen</h1>
<SaveProfileButton />
</main>
</ToastProvider>
);
}
Praxisfälle
Erstens: Einstellungen speichern. Ein Erfolgstoast bestätigt die Aktion ohne Modal. Feldfehler bleiben am Feld; der Toast darf nur zusammenfassen.
Zweitens: Hintergrundjobs wie CSV-Export, KI-Zusammenfassung, Bildverarbeitung oder E-Mail-Versand. Plane Start, Erfolg, Fehler, Abbruch und Retry.
Drittens: Monetarisierung. Ein Toast kann bestätigen, dass ein kostenloses PDF verschickt, eine Beratung angefragt oder ein Download vorbereitet wurde. Er darf Preis, Gumroad-Link, Newsletter-Formular oder mobilen Sticky-Button nicht verdecken. Messe die Wirkung mit Claude Code Analytics Implementation.
Häufige Fehler
Nutze nicht überall role="alert". Nicht dringende Meldungen gehören in status; alert ist für sofort relevante Fehler.
Schließe nicht zu schnell. Fünf Sekunden sind ein guter Standard, Fehler brauchen länger. Hover- und Fokus-Pause gibt Maus- und Tastaturnutzern Zeit.
Lege kritische Informationen nie nur in Toasts ab. Zahlungsfehler, Berechtigungen und Formularvalidierung müssen dauerhaft sichtbar bleiben.
Vermeide Blinken und Endlosanimationen. Die Eintrittsanimation ist kurz und wird bei Reduced Motion abgeschaltet.
Claude-Code-Prompts
Implementiere barrierefreie Toast-Benachrichtigungen mit React + TypeScript.
Bearbeite nur ToastProvider.tsx und toast.css.
Anforderungen:
- success/info/warning/error
- maximal 3 sichtbare Toasts
- Auto-Dismiss, Schließen-Button, Pause bei hover/focus
- role="status" für nicht dringende Meldungen
- role="alert" nur für dringende Fehler
- aria-atomic="true"
- prefers-reduced-motion
- mobile safe-area
- wichtige Formularfehler nie nur im Toast
Verifikation:
- npm run typecheck
- npm run lint
- Erfolg, Fehler, mehr als 3 Toasts, Hover-Pause, Fokus-Pause und Tastatur-Schließen testen
Reviewe diese Toast-Implementierung kritisch.
Prüfe status/alert, Auto-Dismiss-Zeit, hover/focus pause,
reduced motion, mobile safe areas, Tastaturbedienung und verschwindende wichtige Informationen.
Gib Findings nach Schwere mit Datei, Zeile und konkreter Korrektur aus.
Diese Punkte passen in CLAUDE.md Best Practices und die Review Workflow Checklist.
Verifikation
Ich habe das Muster in einem kleinen React-Projekt mit getrennten Dateien getestet: Erfolg, Fehler, mehr als drei Toasts, Hover-Pause, Fokus-Pause, Schließen-Button und Reduced Motion. Die Restzeit liegt in useRef, daher startet der Timer nach einer Pause nicht wieder bei fünf Sekunden. Für Produktion ergänze Playwright, axe und einen manuellen Screenreader-Test.
Fazit
Toasts sind klein, aber sie beeinflussen Barrierefreiheit, Vertrauen und mobile Conversion. Bitte Claude Code nicht nur um eine visuelle Komponente, sondern um Verhalten, Grenzen und Review-Kriterien.
Allein startest du mit dem kostenlosen Claude Code Cheatsheet. Für fertige Prompts und Setup-Materialien nutze die ClaudeCodeLab Produkte. Teams können UI-Review, Accessibility und Monetarisierung über Claude Code Training und Beratung standardisieren.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Permission Safety Ladder: Zugriff kontrolliert erweitern
Von read-only zu begrenzten Änderungen, Prüfbefehlen und Deploy-Checks mit klarer Kontrolle.
Claude Code Small PR Proof Pack: kleine Änderungen reviewbar machen
Ein Proof Pack für Claude-Code-PRs: Diff, Checks, öffentliche URL, CTA-Pfad und Rollback.
Claude-Code-Review-Gate vor dem Commit
Vor dem Commit mit Claude Code prüfen: Diff, Build, öffentliche URL, Gumroad-Links, Beratung-CTA, fehlende Tests und fremde Dateien.