Tips & Tricks (अपडेट: 2/6/2026)

Claude Code के साथ accessible React toast notifications बनाएं

React toast guide: queue, auto-dismiss, pause, aria-live, reduced motion और mobile safe areas के साथ.

Claude Code के साथ accessible React toast notifications बनाएं

Toast notification छोटा UI लगता है: “Saved”, “Export started”, “Payment failed”. लेकिन production में यही छोटा संदेश accessibility, mobile layout, trust और conversion को प्रभावित करता है। खराब toast बहुत जल्दी गायब हो जाता है, हर message को alert बना देता है, mobile CTA को ढक देता है, या important error को सिर्फ एक disappear होने वाले box में रखता है।

Claude Code visual component जल्दी बना देता है। Publish करने लायक implementation के लिए prompt में queue, auto-dismiss, hover/focus pause, role="status" और role="alert" का अंतर, prefers-reduced-motion, mobile safe area और review checklist साफ लिखना जरूरी है। Related reading के लिए Claude Code accessibility, animation implementation, responsive design और React development देखें।

Design rules

Toast modal नहीं है। इसे focus trap नहीं करना चाहिए और user का काम रोकना नहीं चाहिए। इसे short, non-blocking status message के लिए इस्तेमाल करें। अगर user को form field ठीक करना है, destructive action confirm करना है, payment recover करना है या लंबा instruction पढ़ना है, तो message page में भी persistent रहना चाहिए।

इस guide का implementation ये rules follow करता है:

  • एक समय में maximum 3 toasts
  • success, info और warning के लिए role="status"
  • urgent error के लिए ही role="alert"
  • auto-dismiss, लेकिन hover और focus पर pause
  • हर toast में close button
  • prefers-reduced-motion में animation बंद
  • mobile safe area के लिए env(safe-area-inset-*)

Official references: MDN status role, MDN alert role, W3C WCAG Status Messages, W3C WCAG Pause, Stop, Hide, MDN setTimeout, MDN prefers-reduced-motion, और Claude Code docs

Copy-paste React code

ToastProvider.tsx बनाएं। यह सिर्फ React पर निर्भर है। Next.js App Router में file के ऊपर "use client"; जोड़ें।

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="Notifications">
      {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={`Close ${toast.title}`} onClick={() => onDismiss(toast.id)}>
        ×
      </button>
    </section>
  );
}

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; }
}

Use example:

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: "Profile saved",
        description: "Changes will appear next time this screen opens.",
      });
    } catch {
      showToast({
        tone: "error",
        title: "Save failed",
        description: "Check your connection and try again.",
        durationMs: 8000,
      });
    }
  }

  return <button onClick={handleSave}>Save</button>;
}

export default function App() {
  return (
    <ToastProvider>
      <main>
        <h1>Settings</h1>
        <SaveProfileButton />
      </main>
    </ToastProvider>
  );
}

3 real use cases

पहला use case settings screen है। Save के बाद success toast user को भरोसा देता है। लेकिन field validation error field के पास ही रहना चाहिए; toast सिर्फ summary दे सकता है।

दूसरा use case background job है: CSV export, AI summary, image processing या email sending। Claude Code से start, success, failure, retry और cancel states लिखवाएं।

तीसरा use case monetization flow है। Free PDF भेजना, paid download तैयार करना या consultation request receive करना toast से confirm किया जा सकता है। लेकिन toast pricing CTA, Gumroad link, newsletter form या mobile sticky button को cover नहीं करना चाहिए। Impact track करने के लिए Claude Code analytics implementation देखें।

Common pitfalls

हर toast को role="alert" न बनाएं। Save success और copy complete जैसे messages status होने चाहिए। alert सिर्फ urgent failure के लिए रखें।

बहुत जल्दी dismiss न करें। Default 5 seconds ठीक है; errors को 8 seconds दें। Hover/focus pause keyboard users को close button तक पहुंचने का समय देता है।

Important information सिर्फ toast में न रखें। Payment failure, permission problem और form validation page में persistent दिखना चाहिए।

Looping animation और blinking से बचें। Entry animation छोटी हो और reduced motion में बंद हो।

Claude Code prompts

Implement accessible React + TypeScript toast notifications.
Only edit ToastProvider.tsx and toast.css.

Requirements:
- success/info/warning/error
- max 3 visible toasts
- auto-dismiss, close button, pause on hover/focus
- role="status" for non-urgent messages
- role="alert" only for urgent errors
- aria-atomic="true"
- prefers-reduced-motion support
- mobile safe-area support
- no important form error only inside a toast

Verification:
- npm run typecheck
- npm run lint
- test success, error, more than 3 toasts, hover pause, focus pause, keyboard close
Review this toast implementation critically.
Check status/alert, auto-dismiss time, hover/focus pause,
reduced motion, mobile safe areas, keyboard access, and disappearing important information.
Return findings by severity with file, line, and concrete fix.

इन points को CLAUDE.md best practices और review workflow checklist में रखें।

Hands-on verification

मैंने इस pattern को छोटे React project में ToastProvider.tsx और toast.css अलग रखकर test किया: success, error, 3 से ज्यादा toasts, hover pause, focus pause, close button और reduced motion। Remaining time useRef में रहता है, इसलिए pause के बाद timer दोबारा 5 seconds से शुरू नहीं होता। Production में Playwright, axe और manual screen reader check जोड़ें।

Conclusion

Toast छोटा UI है, पर accessibility, trust और mobile conversion पर असर डालता है। Claude Code से सिर्फ visual component न मांगें; behavior, limits और review criteria भी लिखें।

Solo learning के लिए free Claude Code cheatsheet से शुरू करें। Ready prompts और setup material के लिए ClaudeCodeLab products देखें। Team workflow, UI review, accessibility और monetization guardrails के लिए Claude Code training and consultation उपयोगी है।

#Claude Code #toast #notification #React #UI
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.