Announcement bar
A full-width bar with one message, an optional arrow link and a dismiss button.
npx shadcn@latest add https://ui.goodtabs.co/r/announcement-bar.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/announcement-bar
Primary
Pro launches 1 Nov, 40% off for the first weekGet Pro
The solid version for the one thing that matters this week.
Dismissible
Maintenance on Sunday 2 Nov, 02:00 to 03:00 UTCStatus page
Page content
Pass onDismiss to add the close button. The bar removes itself.
Usage
import { AnnouncementBar } from "@/components/ui/announcement-bar"
<AnnouncementBar href="/changelog" linkLabel="See what's new" onDismiss={() => setSeen(true)}>
goodtabs UI 1.0 is out
</AnnouncementBar>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | The message. Keep it to one line. | |
| href | string | Where the arrow link goes. Omit to hide the link. | |
| linkLabel | string | "Learn more" | Text of the arrow link. |
| onDismiss | () => void | Adds the dismiss button. Called after the bar hides itself. | |
| dismissLabel | string | "Dismiss announcement" | Accessible name of the dismiss button. |
| variant | "muted" | "primary" | "muted" | muted sits quietly above a header; primary is the solid version. |
Source
announcement-bar/announcement-bar.tsx
"use client";
import * as React from "react";
import { ArrowRightIcon, XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type AnnouncementBarProps = Omit<React.ComponentProps<"div">, "children"> & {
/** The message. Keep it to one line. */
children: React.ReactNode;
/** Where the arrow link goes. */
href?: string;
/** Text of the arrow link. Defaults to "Learn more" when href is set. */
linkLabel?: string;
/** Adds the dismiss button. The bar removes itself, then calls this. */
onDismiss?: () => void;
/** Accessible name of the dismiss button. */
dismissLabel?: string;
/** "muted" sits quietly above a header; "primary" is the solid version. */
variant?: "muted" | "primary";
};
/**
* A full-width bar for one message, an optional arrow link and a dismiss button.
* Dismissing hides it for this render only; persist the choice in onDismiss if it should stay gone.
*/
export function AnnouncementBar({
children,
href,
linkLabel = "Learn more",
onDismiss,
dismissLabel = "Dismiss announcement",
variant = "muted",
className,
...props
}: AnnouncementBarProps) {
const [dismissed, setDismissed] = React.useState(false);
if (dismissed) return null;
const primary = variant === "primary";
return (
<div
role="region"
aria-label="Announcement"
data-slot="announcement-bar"
data-variant={variant}
className={cn(
"relative flex min-h-10 w-full items-center justify-center gap-x-3 gap-y-0.5 px-10 py-1.5 text-center text-sm",
primary ? "bg-primary text-primary-foreground" : "bg-muted text-foreground",
className,
)}
{...props}
>
<div className="flex flex-wrap items-center justify-center gap-x-3 gap-y-0.5">
<span>{children}</span>
{href && (
<a
href={href}
className={cn(
"group/announcement-link inline-flex shrink-0 items-center gap-1 rounded-sm font-medium outline-none focus-visible:ring-2",
primary
? "focus-visible:ring-primary-foreground/50"
: "text-foreground hover:text-foreground/85 focus-visible:ring-ring/50",
)}
>
{linkLabel}
<ArrowRightIcon
aria-hidden="true"
className="size-3.5 transition-transform duration-150 ease-out group-hover/announcement-link:translate-x-0.5 group-focus-visible/announcement-link:translate-x-0.5 motion-reduce:transition-none motion-reduce:group-hover/announcement-link:translate-x-0"
/>
</a>
)}
</div>
{onDismiss && (
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={dismissLabel}
onClick={() => {
setDismissed(true);
onDismiss();
}}
className={cn(
"absolute top-1/2 right-1.5 -translate-y-1/2 rounded-full",
primary &&
"text-primary-foreground hover:bg-primary-foreground/15 hover:text-primary-foreground focus-visible:ring-primary-foreground/50",
)}
>
<XIcon aria-hidden="true" />
</Button>
)}
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/announcement-bar/prompt.md, or the Copy AI prompt button above.
When to use
- One launch, offer or maintenance notice above the header
- A site-wide message that should be easy to close
- Pointing to a changelog or status page without a modal
When not to use
- Errors or warnings tied to a form or action, use an inline alert
- More than one message at a time, a bar is for one thing
- Marketing copy that changes often, people learn to ignore it
Agent notes
- Client component; a demo or page that passes onDismiss must be a client component too
- Dismissing only hides it for this render. Store the choice (cookie, localStorage) in onDismiss and do not render it next time
- It has no border and no radius, so it sits flush at the top of the page or inside a frame with overflow-hidden
- The link inherits the bar's text colour; on primary it stays primary-foreground