Announcement

New Spruce UI 2 is here, with 40 new components. Read the release notes

@use 'sprucecss/scss/spruce' as *;

.site-header-announcement {
  backdrop-filter: blur(12px);
  background-color: color-alpha('background', 0.85);
  border-block-end: config('border-width-base', $display) solid color('border');
  position: relative;

  &__banner {
    align-items: center;
    background-color: color('primary-background', 'btn');
    color: color('primary-foreground', 'btn');
    display: flex;
    font-size: config('font-size-sm', $typography);
    gap: spacer('s');
    justify-content: center;
    padding: spacer('xs') spacer('xl');
    position: relative;
    text-align: center;
    @include selection(color('secondary-background', 'btn'), color('secondary-foreground', 'btn'));

    &[hidden] {
      display: none;
    }

    p {
      margin: 0;
    }

    a {
      color: inherit;
      font-weight: 700;
      text-underline-offset: 0.2em;
    }

    .badge {
      --badge-color: #{color('primary-foreground', 'btn')};
      color: color('primary-background', 'btn');
      margin-inline-end: spacer('xxs');
    }
  }

  &__close {
    @include clear-btn;
    align-items: center;
    block-size: 2rem;
    border-radius: radius('sm');
    color: inherit;
    display: flex;
    inline-size: 2rem;
    inset-block-start: 50%;
    inset-inline-end: spacer('xs');
    justify-content: center;
    position: absolute;
    translate: 0 -50%;

    &[hidden] {
      display: none;
    }

    &:hover {
      background-color: hsl(0deg 0% 100% / 15%);
    }

    &:focus-visible {
      outline: 2px solid currentcolor;
      outline-offset: 1px;
    }

    svg {
      block-size: 1rem;
      inline-size: 1rem;
    }
  }

  &__inner {
    align-items: center;
    display: flex;
    gap: spacer-clamp('s', 'l');
    min-block-size: 4.5rem;
  }

  &__logo {
    align-items: center;
    color: color('heading');
    display: inline-flex;
    font-family: config('font-family-heading', $typography);
    font-size: font-size('h5');
    font-weight: config('font-weight-heading', $typography);
    gap: spacer('xs');
    text-decoration: none;

    img {
      block-size: 2rem;
      inline-size: 2rem;
    }
  }

  // The links are centered between the logo and the actions from md.
  &__navigation {
    margin-inline-start: auto;
    order: 1;

    @include breakpoint('md') {
      margin-inline: auto;
      order: 0;
    }
  }

  &__actions {
    align-items: center;
    display: flex;
    gap: spacer('xs');
    margin-inline-start: auto;

    @include breakpoint('md') {
      margin-inline-start: 0;
    }
  }

  // "Log in" is a button on wide screens and a menu link on small ones.
  &__login {
    display: none;

    @include breakpoint('md') {
      display: inline-flex;
    }
  }

  &__mobile-only {
    @include breakpoint('md') {
      display: none;
    }
  }
}
<a class="skip-link sr-only-focusable" href="#content">Skip to content</a>
<div class="site-header-announcement__banner" role="region" aria-label="Announcement" data-announcement="launch-2026-10">
    <p><span class="badge badge--solid">New</span> Spruce UI 2 is here, with 40 new components. <a href="#">Read the release notes<span aria-hidden="true"> →</span></a></p>
    <button class="site-header-announcement__close" type="button" hidden data-announcement-close>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M18 6 6 18M6 6l12 12"/></svg>
        <span class="sr-only">Dismiss the announcement</span>
    </button>
</div>
<header class="site-header-announcement">
    <div class="container">
        <div class="site-header-announcement__inner">
            <a class="site-header-announcement__logo" href="#">
                <img src="/img/cone-logo.svg" alt="" width="40" height="40">
                <span>Cone</span>
            </a>
            <nav class="site-navigation site-header-announcement__navigation" aria-label="Main" data-site-navigation>
                <button class="btn btn--secondary btn--icon site-navigation__btn" type="button" aria-controls="announcement-menu" aria-expanded="false">
                    <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path class="site-navigation__line site-navigation__line--top" d="M4 6h16"/><path class="site-navigation__line site-navigation__line--middle" d="M4 12h16"/><path class="site-navigation__line site-navigation__line--bottom" d="M4 18h16"/></svg>
                    <span class="sr-only">Menu</span>
                </button>
                <ul class="navigation-menu" id="announcement-menu">
                    <li class="site-navigation__item">
                        <button class="site-navigation__toggle" type="button" aria-expanded="false" aria-controls="announcement-submenu-product">
                            Product
                            <svg class="site-navigation__chevron" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m6 9 6 6 6-6"/></svg>
                        </button>
                        <ul class="site-navigation__submenu" id="announcement-submenu-product">
                            <li><a href="#">Features</a></li>
                            <li><a href="#">Integrations</a></li>
                            <li><a href="#">Changelog</a></li>
                            <li><a href="#">Roadmap</a></li>
                        </ul>
                    </li>
                    <li class="site-navigation__item">
                        <button class="site-navigation__toggle" type="button" aria-expanded="false" aria-controls="announcement-submenu-solutions">
                            Solutions
                            <svg class="site-navigation__chevron" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m6 9 6 6 6-6"/></svg>
                        </button>
                        <ul class="site-navigation__submenu" id="announcement-submenu-solutions">
                            <li><a href="#">Startups</a></li>
                            <li><a href="#">Enterprise</a></li>
                            <li><a href="#">Agencies</a></li>
                        </ul>
                    </li>
                    <li><a href="#" aria-current="page">Pricing</a></li>
                    <li><a href="#">Docs</a></li>
                    <li><a href="#">Blog</a></li>
                    <li class="site-header-announcement__mobile-only"><a href="#">Log in</a></li>
                </ul>
            </nav>
            <div class="site-header-announcement__actions">
                <a class="btn btn--ghost-primary btn--sm site-header-announcement__login" href="#">Log in</a>
                <a class="btn btn--primary btn--sm" href="#">Get started</a>
            </div>
        </div>
    </div>
</header>
(() => {
  document.querySelectorAll('[data-announcement]').forEach((banner) => {
    const key = `announcement-${banner.dataset.announcement}`;
    const close = banner.querySelector('[data-announcement-close]');

    // A dismissed announcement stays hidden; a new one gets a new data-announcement id.
    try {
      if (localStorage.getItem(key) === 'dismissed') {
        banner.hidden = true;
        return;
      }
    } catch (error) {}

    close.hidden = false;
    close.addEventListener('click', () => {
      banner.hidden = true;

      try {
        localStorage.setItem(key, 'dismissed');
      } catch (error) {}

      // The button is gone; continue from the header.
      document.querySelector('.site-header-announcement__logo')?.focus();
    });
  });
})();

Announcement

A product site header with a dismissible announcement bar above it, the navigation centered between the logo and the "Log in" and "Get started" buttons.

Anatomy

  • .site-header-announcement__banner: a region with a Badge, the message, a link and the __close button. Its data-announcement id identifies the message.
  • .site-header-announcement__inner: the __logo, the Site Navigation and the __actions.

Accessibility and Usability

  • The banner is a labeled region ("Announcement"), so it can be found and skipped like the other landmarks; it comes before the header, after the Skip Link.
  • The close button has hidden text ("Dismiss the announcement"); after closing, the focus moves to the logo link instead of getting lost.
  • The arrow after the link text is decorative (aria-hidden).
  • On small screens, "Log in" moves into the menu, and "Get started" stays visible next to the menu button.

Technical Details

  • Set the message with an announcement object (id, badge, text, link).
  • A dismissed announcement is saved in localStorage by its id and stays hidden; publish a new message with a new id to show it again. Without JavaScript, the banner shows and the close button stays hidden.
  • To avoid a flash of a dismissed banner, hide it with an inline script in the head, or render it on the server based on a cookie.
  • The header is translucent with a backdrop blur; add position: sticky and inset-block-start: 0 to keep it at the top.
  • "Product" and "Solutions" have dropdowns (the Site Navigation submenus).

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.