banner
uiDismissible announcement banner with optional rainbow gradient animation. Persists dismissal in localStorage.
fumadocs/banner•v0.1.0•react, next
Preview
document0 v0.4.0 is now available - check out these sweet fumadocs components!.
Introducing the plugin registry — share and discover docs components/plugins.
Installation
$npx @document0/cli add fumadocs/banner
This will also install: lucide-react@>=0.300.0, tailwind-merge@>=2.0.0
Usage
import { Banner } from "./components/fumadocs/banner";
// Example usage in your layout or page:
<Banner />Source
After installation, this lives at components/fumadocs/banner/Banner.tsx and you can modify it however you like.
"use client";
import { type HTMLAttributes, useEffect, useState } from "react";
import { X } from "lucide-react";
import { twMerge } from "tailwind-merge";
type BannerVariant = "rainbow" | "normal";
export function Banner({
id,
variant = "normal",
changeLayout = true,
height = "3rem",
rainbowColors = [
"rgba(0,149,255,0.56)",
"rgba(231,77,255,0.77)",
"rgba(255,0,0,0.73)",
"rgba(131,255,166,0.66)",
],
...props
}: HTMLAttributes<HTMLDivElement> & {
height?: string;
variant?: BannerVariant;
rainbowColors?: string[];
changeLayout?: boolean;
}) {
const [open, setOpen] = useState(true);
const globalKey = id ? `nd-banner-${id}` : null;
useEffect(() => {
if (globalKey) setOpen(localStorage.getItem(globalKey) !== "true");
}, [globalKey]);
if (!open) return null;
return (
<div
id={id}
{...props}
className={twMerge(
"sticky top-0 z-40 flex flex-row items-center justify-center px-4 text-center text-sm font-medium",
variant === "normal" && "bg-secondary",
variant === "rainbow" && "bg-background",
!open && "hidden",
props.className,
)}
style={{ height }}
>
{changeLayout && open ? (
<style>
{globalKey
? `:root:not(.${globalKey}) { --fd-banner-height: ${height}; }`
: `:root { --fd-banner-height: ${height}; }`}
</style>
) : null}
{globalKey ? <style>{`.${globalKey} #${id} { display: none; }`}</style> : null}
{globalKey ? (
<script
dangerouslySetInnerHTML={{
__html: `if (localStorage.getItem('${globalKey}') === 'true') document.documentElement.classList.add('${globalKey}');`,
}}
/>
) : null}
{variant === "rainbow" ? flow({ colors: rainbowColors }) : null}
{props.children}
{id ? (
<button
type="button"
aria-label="Close Banner"
onClick={() => {
setOpen(false);
if (globalKey) localStorage.setItem(globalKey, "true");
}}
className="absolute end-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-md p-1.5 text-muted-foreground/50 transition-colors hover:bg-accent hover:text-accent-foreground"
>
<X className="size-4" />
</button>
) : null}
</div>
);
}
const maskImage =
"linear-gradient(to bottom,white,transparent), radial-gradient(circle at top center, white, transparent)";
function flow({ colors }: { colors: string[] }) {
return (
<>
<div
className="absolute inset-0 z-[-1]"
style={
{
maskImage,
maskComposite: "intersect",
animation: "fd-moving-banner 20s linear infinite",
backgroundImage: `repeating-linear-gradient(70deg, ${[...colors, colors[0]].map((color, i) => `${color} ${(i * 50) / colors.length}%`).join(", ")})`,
backgroundSize: "200% 100%",
filter: "saturate(2)",
} as object
}
/>
<style>
{`@keyframes fd-moving-banner {
from { background-position: 0% 0; }
to { background-position: 100% 0; }
}`}
</style>
</>
);
}
Tags
bannerannouncementreact