# Announcement Bar

> A dismissible top announcement banner with a badge, message, arrow link and close button.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/announcement-bar
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/announcement-bar.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/announcement-bar-demo.tsx"
"use client"

import * as React from "react"

import { AnnouncementBar } from "@/components/announcement-bar"

export default function AnnouncementBarDemo() {
  const [dismissed, setDismissed] = React.useState(false)

  return (
    <div className="w-full max-w-3xl overflow-hidden rounded-2xl border bg-background shadow-sm">
      <AnnouncementBar
        dismissed={dismissed}
        onDismiss={() => setDismissed(true)}
        badge="New"
        message="Atelier 2.0 is here with 40+ new components and a faster registry."
        linkLabel="Read the changelog"
      />
      <div className="grid gap-3 p-8">
        <div className="h-4 w-1/3 rounded bg-muted" />
        <div className="h-3 w-2/3 rounded bg-muted" />
        <div className="h-3 w-1/2 rounded bg-muted" />
        {dismissed && (
          <button
            type="button"
            onClick={() => setDismissed(false)}
            className="mt-4 inline-flex h-9 w-fit items-center rounded-lg border bg-background px-4 text-sm font-medium outline-none transition-colors hover:bg-accent focus-visible:ring-4 focus-visible:ring-ring/40"
          >
            Show again
          </button>
        )}
      </div>
    </div>
  )
}
```

## Source

```tsx title="components/announcement-bar.tsx"
"use client"

import * as React from "react"
import { ArrowRight, X } from "lucide-react"

import { cn } from "@/lib/utils"

export interface AnnouncementBarProps extends Omit<React.ComponentProps<"div">, "children"> {
  badge?: string
  message: React.ReactNode
  linkLabel?: string
  href?: string
  /** Controlled dismissed state */
  dismissed?: boolean
  /** Called when the user closes the bar */
  onDismiss?: () => void
}

export function AnnouncementBar({
  badge = "New",
  message,
  linkLabel = "Learn more",
  href = "#",
  dismissed,
  onDismiss,
  className,
  ...props
}: AnnouncementBarProps) {
  const [internal, setInternal] = React.useState(false)
  const hidden = dismissed ?? internal

  const dismiss = () => {
    setInternal(true)
    onDismiss?.()
  }

  if (hidden) return null

  return (
    <div
      role="region"
      aria-label="Announcement"
      className={cn(
        "relative flex w-full items-center justify-center gap-3 border-b bg-secondary px-12 py-2.5 text-sm text-secondary-foreground",
        className
      )}
      {...props}
    >
      <div className="flex min-w-0 flex-wrap items-center justify-center gap-x-3 gap-y-1">
        {badge && (
          <span className="rounded-full bg-primary px-2 py-0.5 text-xs font-semibold text-primary-foreground">{badge}</span>
        )}
        <span className="min-w-0 text-center">{message}</span>
        <a
          href={href}
          className="group inline-flex items-center gap-1 rounded-md font-medium underline-offset-4 outline-none hover:underline focus-visible:ring-4 focus-visible:ring-ring/40"
        >
          {linkLabel}
          <ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" aria-hidden />
        </a>
      </div>
      <button
        type="button"
        onClick={dismiss}
        aria-label="Dismiss announcement"
        className="absolute right-3 top-1/2 inline-flex size-7 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-4 focus-visible:ring-ring/40"
      >
        <X className="size-4" aria-hidden />
      </button>
    </div>
  )
}
```
