Web InnoventixFreeCode

Left Accent Alert

Original · free

Alerts with a coloured left accent bar and icon.

byWeb InnoventixReact + Tailwind
alertleftaccentalerts
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/alert-left-accent.json
alert-left-accent.tsx
"use client";

import { useState, type ReactElement } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

type Tone = "info" | "success" | "warning" | "error";

type AlertItem = {
  id: string;
  tone: Tone;
  title: string;
  message: string;
  action?: { label: string; href: string };
};

type ToneStyle = {
  accent: string;
  panel: string;
  iconWrap: string;
  title: string;
  body: string;
  action: string;
  focusRing: string;
  Icon: () => ReactElement;
};

const InfoIcon = () => (
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
    <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.75" />
    <path d="M12 11v5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
    <circle cx="12" cy="7.75" r="1.15" fill="currentColor" />
  </svg>
);

const SuccessIcon = () => (
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
    <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.75" />
    <path d="m8.25 12.4 2.4 2.4 4.85-5.1" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);

const WarningIcon = () => (
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
    <path d="M12 3.4 21 19H3l9-15.6Z" stroke="currentColor" strokeWidth="1.75" strokeLinejoin="round" />
    <path d="M12 9.5v4" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
    <circle cx="12" cy="16.4" r="1.1" fill="currentColor" />
  </svg>
);

const ErrorIcon = () => (
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
    <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.75" />
    <path d="m9.25 9.25 5.5 5.5M14.75 9.25l-5.5 5.5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
  </svg>
);

const TONES: Record<Tone, ToneStyle> = {
  info: {
    accent: "bg-sky-500 dark:bg-sky-400",
    panel: "bg-sky-50 dark:bg-sky-950/40",
    iconWrap: "text-sky-600 dark:text-sky-300",
    title: "text-sky-900 dark:text-sky-100",
    body: "text-sky-700/90 dark:text-sky-200/80",
    action: "text-sky-700 hover:text-sky-900 dark:text-sky-300 dark:hover:text-sky-100 decoration-sky-400/50",
    focusRing: "focus-visible:ring-sky-500 dark:focus-visible:ring-sky-400",
    Icon: InfoIcon,
  },
  success: {
    accent: "bg-emerald-500 dark:bg-emerald-400",
    panel: "bg-emerald-50 dark:bg-emerald-950/40",
    iconWrap: "text-emerald-600 dark:text-emerald-300",
    title: "text-emerald-900 dark:text-emerald-100",
    body: "text-emerald-700/90 dark:text-emerald-200/80",
    action: "text-emerald-700 hover:text-emerald-900 dark:text-emerald-300 dark:hover:text-emerald-100 decoration-emerald-400/50",
    focusRing: "focus-visible:ring-emerald-500 dark:focus-visible:ring-emerald-400",
    Icon: SuccessIcon,
  },
  warning: {
    accent: "bg-amber-500 dark:bg-amber-400",
    panel: "bg-amber-50 dark:bg-amber-950/40",
    iconWrap: "text-amber-600 dark:text-amber-300",
    title: "text-amber-900 dark:text-amber-100",
    body: "text-amber-800/90 dark:text-amber-200/80",
    action: "text-amber-800 hover:text-amber-950 dark:text-amber-300 dark:hover:text-amber-100 decoration-amber-400/50",
    focusRing: "focus-visible:ring-amber-500 dark:focus-visible:ring-amber-400",
    Icon: WarningIcon,
  },
  error: {
    accent: "bg-rose-500 dark:bg-rose-400",
    panel: "bg-rose-50 dark:bg-rose-950/40",
    iconWrap: "text-rose-600 dark:text-rose-300",
    title: "text-rose-900 dark:text-rose-100",
    body: "text-rose-700/90 dark:text-rose-200/80",
    action: "text-rose-700 hover:text-rose-900 dark:text-rose-300 dark:hover:text-rose-100 decoration-rose-400/50",
    focusRing: "focus-visible:ring-rose-500 dark:focus-visible:ring-rose-400",
    Icon: ErrorIcon,
  },
};

const INITIAL_ALERTS: AlertItem[] = [
  {
    id: "deploy",
    tone: "success",
    title: "Deployment finished",
    message: "webinnoventix.com is live on the edge network. All 214 pages passed the post-build health check.",
    action: { label: "View build log", href: "#build-log" },
  },
  {
    id: "ssl",
    tone: "info",
    title: "Certificate renews automatically",
    message: "Your SSL certificate renews on 2 Aug 2026. No action is needed — we handle the renewal 30 days early.",
  },
  {
    id: "quota",
    tone: "warning",
    title: "Image CDN nearing its limit",
    message: "You have used 88% of this month's 100 GB transfer allowance. Traffic beyond the cap is billed at $0.04/GB.",
    action: { label: "Upgrade plan", href: "#billing" },
  },
  {
    id: "form",
    tone: "error",
    title: "Contact form is not sending",
    message: "The RESEND_API_KEY environment variable is missing, so 6 lead submissions are queued and undelivered.",
    action: { label: "Add API key", href: "#env" },
  },
];

export default function AlertLeftAccent() {
  const reduceMotion = useReducedMotion();
  const [alerts, setAlerts] = useState<AlertItem[]>(INITIAL_ALERTS);

  const dismiss = (id: string) => {
    setAlerts((prev) => prev.filter((a) => a.id !== id));
  };

  const reset = () => setAlerts(INITIAL_ALERTS);

  return (
    <section className="relative w-full bg-white px-4 py-20 sm:px-6 dark:bg-neutral-950">
      <style>{`
        @keyframes wialaAccentPulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.55; }
        }
        .wiala-live-accent { animation: wialaAccentPulse 2.6s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .wiala-live-accent { animation: none; }
        }
      `}</style>

      <div className="mx-auto max-w-2xl">
        <header className="mb-8">
          <span className="inline-block text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600 dark:text-indigo-400">
            System status
          </span>
          <h2 className="mt-2 text-2xl font-bold tracking-tight text-neutral-900 sm:text-3xl dark:text-neutral-50">
            Left-accent alerts
          </h2>
          <p className="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
            Four tones, each with a coloured accent bar and icon. Every alert can be dismissed or acted on.
          </p>
        </header>

        <div className="space-y-3.5">
          <AnimatePresence initial={false} mode="popLayout">
            {alerts.map((alert) => {
              const t = TONES[alert.tone];
              const Icon = t.Icon;
              const isError = alert.tone === "error";
              return (
                <motion.div
                  key={alert.id}
                  layout={reduceMotion ? false : true}
                  initial={reduceMotion ? false : { opacity: 0, x: -16, height: 0 }}
                  animate={reduceMotion ? { opacity: 1 } : { opacity: 1, x: 0, height: "auto" }}
                  exit={reduceMotion ? { opacity: 0 } : { opacity: 0, x: 24, height: 0, marginBottom: 0 }}
                  transition={reduceMotion ? { duration: 0 } : { type: "spring", stiffness: 380, damping: 34 }}
                  role={isError ? "alert" : "status"}
                  aria-live={isError ? "assertive" : "polite"}
                  className={`relative flex gap-3 overflow-hidden rounded-lg border border-black/5 pl-4 pr-3 py-3.5 shadow-sm ring-1 ring-black/[0.02] dark:border-white/10 dark:ring-white/[0.02] ${t.panel}`}
                >
                  <span
                    aria-hidden="true"
                    className={`absolute inset-y-0 left-0 w-1.5 ${t.accent} ${isError ? "wiala-live-accent" : ""}`}
                  />

                  <span className={`mt-0.5 shrink-0 ${t.iconWrap}`}>
                    <Icon />
                  </span>

                  <div className="min-w-0 flex-1">
                    <p className={`text-sm font-semibold ${t.title}`}>{alert.title}</p>
                    <p className={`mt-1 text-sm leading-relaxed ${t.body}`}>{alert.message}</p>
                    {alert.action && (
                      <a
                        href={alert.action.href}
                        className={`mt-2.5 inline-flex items-center gap-1 rounded text-sm font-semibold underline underline-offset-4 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent ${t.action} ${t.focusRing}`}
                      >
                        {alert.action.label}
                        <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-3.5 w-3.5">
                          <path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
                        </svg>
                      </a>
                    )}
                  </div>

                  <button
                    type="button"
                    onClick={() => dismiss(alert.id)}
                    aria-label={`Dismiss ${alert.title}`}
                    className={`-mr-1 mt-0.5 grid h-7 w-7 shrink-0 place-items-center rounded-md text-neutral-500 outline-none transition-colors hover:bg-black/5 hover:text-neutral-800 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-transparent dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-neutral-100 ${t.focusRing}`}
                  >
                    <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4">
                      <path d="M6 6l12 12M18 6 6 18" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" />
                    </svg>
                  </button>
                </motion.div>
              );
            })}
          </AnimatePresence>
        </div>

        <AnimatePresence>
          {alerts.length === 0 && (
            <motion.div
              initial={reduceMotion ? false : { opacity: 0, y: 8 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0 }}
              className="rounded-lg border border-dashed border-neutral-300 bg-neutral-50 px-4 py-10 text-center dark:border-neutral-700 dark:bg-neutral-900"
            >
              <p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
                You are all caught up — no active alerts.
              </p>
              <button
                type="button"
                onClick={reset}
                className="mt-4 inline-flex items-center gap-1.5 rounded-md bg-indigo-600 px-3.5 py-2 text-sm font-semibold text-white outline-none transition-colors hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950"
              >
                <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4">
                  <path d="M4 12a8 8 0 1 1 2.3 5.6M4 12V7m0 5h5" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Restore alerts
              </button>
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →