Web InnoventixFreeCode

Banner Top Alert

Original · free

A full-width top announcement banner, dismissible.

byWeb InnoventixReact + Tailwind
alertbannertopalerts
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-banner-top.json
alert-banner-top.tsx
"use client";

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

export default function AlertBannerTop() {
  const [open, setOpen] = useState<boolean>(true);
  const reduceMotion = useReducedMotion();

  return (
    <section className="relative w-full">
      <style>{`
        @keyframes abt_shimmer {
          0% { background-position: -160% 0; }
          100% { background-position: 260% 0; }
        }
        @keyframes abt_pulse {
          0%, 100% { transform: scale(1); opacity: 1; }
          50% { transform: scale(1.6); opacity: 0.35; }
        }
        @media (prefers-reduced-motion: reduce) {
          .abt_shimmer, .abt_pulse { animation: none !important; }
        }
      `}</style>

      <AnimatePresence initial={false}>
        {open && (
          <motion.div
            key="abt-banner"
            role="region"
            aria-label="Site announcement"
            initial={reduceMotion ? false : { height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={reduceMotion ? { opacity: 0 } : { height: 0, opacity: 0 }}
            transition={{ duration: 0.42, ease: [0.22, 1, 0.36, 1] }}
            className="relative w-full overflow-hidden border-b border-indigo-400/30 bg-gradient-to-r from-indigo-600 via-violet-600 to-indigo-600 text-white dark:border-white/10 dark:from-indigo-700 dark:via-violet-700 dark:to-indigo-700"
          >
            {/* animated shimmer sweep */}
            <div
              aria-hidden="true"
              className="abt_shimmer pointer-events-none absolute inset-0 opacity-40 mix-blend-overlay"
              style={{
                backgroundImage:
                  "linear-gradient(105deg, transparent 35%, rgba(255,255,255,0.55) 50%, transparent 65%)",
                backgroundSize: "220% 100%",
                animation: "abt_shimmer 6.5s linear infinite",
              }}
            />

            <div className="relative mx-auto flex max-w-6xl items-center gap-3 px-4 py-2.5 sm:px-6">
              {/* live status dot */}
              <span
                aria-hidden="true"
                className="relative hidden h-2 w-2 shrink-0 items-center justify-center sm:flex"
              >
                <span
                  className="abt_pulse absolute inset-0 rounded-full bg-emerald-300"
                  style={{ animation: "abt_pulse 2.4s ease-in-out infinite" }}
                />
                <span className="relative h-2 w-2 rounded-full bg-emerald-300 ring-2 ring-emerald-300/40" />
              </span>

              {/* badge + message */}
              <p className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-sm leading-snug">
                <span className="inline-flex items-center gap-1.5 rounded-full bg-white/15 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-white ring-1 ring-inset ring-white/25 backdrop-blur-sm">
                  <svg
                    viewBox="0 0 24 24"
                    className="h-3 w-3"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="2.2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    aria-hidden="true"
                  >
                    <path d="m3 11 18-8-8 18-2-8-8-2Z" />
                  </svg>
                  New
                </span>
                <span className="font-medium text-white">
                  We just shipped a faster build pipeline.
                </span>
                <span className="text-white/80">
                  Ship production sites in half the time.
                </span>
              </p>

              {/* CTA */}
              <a
                href="/changelog"
                className="group hidden shrink-0 items-center gap-1.5 rounded-full bg-white px-3.5 py-1.5 text-xs font-semibold text-indigo-700 shadow-sm outline-none transition hover:bg-white/90 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-600 sm:inline-flex"
              >
                Read the notes
                <svg
                  viewBox="0 0 24 24"
                  className="h-3.5 w-3.5 transition-transform duration-200 group-hover:translate-x-0.5"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2.4"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  aria-hidden="true"
                >
                  <path d="M5 12h14" />
                  <path d="m13 6 6 6-6 6" />
                </svg>
              </a>

              {/* dismiss */}
              <button
                type="button"
                onClick={() => setOpen(false)}
                aria-label="Dismiss announcement"
                className="shrink-0 rounded-full p-1.5 text-white/80 outline-none transition hover:bg-white/15 hover:text-white focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-600"
              >
                <svg
                  viewBox="0 0 24 24"
                  className="h-4 w-4"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2.4"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  aria-hidden="true"
                >
                  <path d="M18 6 6 18" />
                  <path d="m6 6 12 12" />
                </svg>
              </button>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      {/* restore control — demo affordance so the banner can be brought back */}
      {!open && (
        <div className="flex w-full items-center justify-center px-4 py-6">
          <button
            type="button"
            onClick={() => setOpen(true)}
            className="inline-flex items-center gap-2 rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm outline-none transition hover:border-indigo-400 hover:text-indigo-600 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:border-indigo-500 dark:hover:text-indigo-400 dark:focus-visible:ring-offset-zinc-950"
          >
            <svg
              viewBox="0 0 24 24"
              className="h-4 w-4"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
              aria-hidden="true"
            >
              <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
              <path d="M3 3v5h5" />
            </svg>
            Restore announcement banner
          </button>
        </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 →