Announcement Bar

Restrained product update strip with gradient sheen and a clean collapse dismiss.

Add to your project

Pick the way you like to build.

Let your AI assistant add it

Copy this prompt and paste it into the AI tool building your site.

# Integrate this UI component

Add this component to my project.

1. Install any missing dependencies.
2. Make sure Tailwind CSS is set up and scans the new file.
3. Prefer passing props over editing the component source — changing the component itself may break it.
4. Adjust colors, size, and layout via props or wrapper classes to match my project. You know my codebase better than this snippet does.

## Component source

```tsx
'use client';

import { AnimatePresence, motion } from 'framer-motion';
import { useState, type ReactNode } from 'react';

// ---------------------------------------------------------------------------
// Props
// ---------------------------------------------------------------------------
//
// message       Bar copy
// action        Optional CTA — { label, href?, onClick? }
// icon          Optional leading mark
// dismissible   Show collapse dismiss control
// tone          neutral | accent | inverse
// sticky        Stick to the top of the viewport
// onDismiss     Fired after collapse
// className     Styles for the outer shell
//
type AnnouncementAction = {
  label: string;
  href?: string;
  onClick?: () => void;
};

type AnnouncementBarProps = {
  message?: string;
  action?: AnnouncementAction;
  icon?: ReactNode;
  dismissible?: boolean;
  tone?: 'neutral' | 'accent' | 'inverse';
  sticky?: boolean;
  onDismiss?: () => void;
  className?: string;
};

const TONE = {
  neutral:
    'border-gray-200/80 bg-linear-to-r from-white via-gray-50 to-white text-gray-800',
  accent:
    'border-rose-200/60 bg-linear-to-r from-rose-50 via-white to-amber-50 text-rose-950',
  inverse:
    'border-gray-800 bg-linear-to-r from-gray-950 via-gray-900 to-gray-950 text-white',
};

export default function AnnouncementBar({
  message = 'New: Coverflow Gallery is live — explore the catalog.',
  action = { label: "See what's new", href: '#' },
  icon,
  dismissible = true,
  tone = 'neutral',
  sticky = false,
  onDismiss,
  className = '',
}: AnnouncementBarProps) {
  const [open, setOpen] = useState(true);
  const inverse = tone === 'inverse';

  return (
    <AnimatePresence initial={false}>
      {open ? (
        <motion.div
          className={[
            'w-full overflow-hidden border-b',
            sticky ? 'sticky top-0 z-50' : '',
            TONE[tone],
            className,
          ]
            .filter(Boolean)
            .join(' ')}
          initial={{ height: 0, opacity: 0 }}
          animate={{ height: 'auto', opacity: 1 }}
          exit={{ height: 0, opacity: 0 }}
          transition={{ duration: 0.35, ease: [0.22, 1, 0.36, 1] }}
        >
          <div className='relative mx-auto flex max-w-5xl items-center justify-center gap-3 px-4 py-2.5 sm:px-6'>
            <span
              className='pointer-events-none absolute inset-0 bg-[linear-gradient(105deg,transparent_40%,rgba(255,255,255,0.35)_50%,transparent_60%)] bg-size-[200%_100%] opacity-40'
              aria-hidden
            />

            {icon ? (
              <span className='relative shrink-0 text-sm' aria-hidden>
                {icon}
              </span>
            ) : (
              <span
                className={[
                  'relative h-1.5 w-1.5 shrink-0 rounded-full',
                  inverse ? 'bg-white/70' : 'bg-rose-400',
                ].join(' ')}
                aria-hidden
              />
            )}

            <p className='relative text-center text-xs font-medium tracking-wide sm:text-[13px]'>
              {message}
              {action ? (
                <>
                  {' '}
                  <a
                    href={action.href ?? '#'}
                    onClick={action.onClick}
                    className={[
                      'inline-flex items-center gap-1 underline-offset-4 transition-[opacity,transform] hover:underline',
                      inverse ? 'text-white/90' : 'text-gray-900',
                    ].join(' ')}
                  >
                    {action.label}
                    <motion.span
                      aria-hidden
                      className='inline-block'
                      whileHover={{ x: 2 }}
                    >
                      →
                    </motion.span>
                  </a>
                </>
              ) : null}
            </p>

            {dismissible ? (
              <button
                type='button'
                aria-label='Dismiss announcement'
                onClick={() => {
                  setOpen(false);
                  window.setTimeout(() => onDismiss?.(), 320);
                }}
                className={[
                  'absolute right-3 inline-flex h-7 w-7 items-center justify-center rounded-full transition-colors sm:right-4',
                  inverse
                    ? 'text-white/50 hover:bg-white/10 hover:text-white'
                    : 'text-gray-400 hover:bg-gray-100 hover:text-gray-700',
                ].join(' ')}
              >
                <svg viewBox='0 0 12 12' className='h-3 w-3' aria-hidden>
                  <path
                    d='M3 3l6 6M9 3L3 9'
                    stroke='currentColor'
                    strokeWidth='1.4'
                    strokeLinecap='round'
                  />
                </svg>
              </button>
            ) : null}
          </div>
        </motion.div>
      ) : null}
    </AnimatePresence>
  );
}

```

Works with your AI tools

ClaudeClaude*
CodexCodex*
LovableLovable
Base44Base44
Figma MakeFigma Make
ReplitReplit*
CursorCursor*
v0v0

* Compatible with most projects that use the default React/Next.js framework.

Built With

ReactTailwind CSSFramer Motion

Props

PropTypeDescription
messagestringBar copy
actionAnnouncementActionOptional CTA — { label, href?, onClick? }
iconReactNodeOptional leading mark
dismissiblebooleanShow the collapse dismiss control
tone'neutral' | 'accent' | 'inverse'Color treatment for the strip
stickybooleanStick to the top of the viewport
onDismiss() => voidFired after the collapse animation
classNamestringStyles for the outer shell

More Navigation Components

Minimal locale control with scaled blur-sm menu and staggered options — no native select chrome.

Compact sun and moon switch that persists theme preference and toggles the document dark class.