Centered Scaling Navigation Bar

A centered floating pill navigation that expands downward via grid-template-rows to reveal links and a scrolling marquee banner. Links slide up from below with staggered delays. A dark overlay fades in behind the pill when the nav is open. Entirely CSS-driven.

navigationcss animationmarqueecenteredpillno gsap

Code

HTML
html
<nav data-navigation-status="not-active" class="navigation">
  <div data-navigation-toggle="close" class="navigation__dark-bg"></div>
  <div class="centered-nav">
    <div class="centered-nav__bg"></div>
    <div class="centered-nav__header">
      <a href="#" class="centered-nav__logo">
        <!-- your logo SVG -->
      </a>
      <button data-navigation-toggle="toggle" class="centered-nav__toggle">
        <div class="centered-nav__toggle-bar"></div>
        <div class="centered-nav__toggle-bar"></div>
      </button>
    </div>
    <div class="centered-nav__content">
      <div class="centered-nav__inner">
        <ul class="centered-nav__ul">
          <div data-navigation-item="" class="centered-nav__li">
            <a href="#" class="hamburger-nav__a">
              <p class="hamburger-nav__p">Home</p>
            </a>
          </div>
          <div data-navigation-item="" class="centered-nav__li">
            <a href="#" class="hamburger-nav__a">
              <p class="hamburger-nav__p">Portfolio</p>
            </a>
          </div>
          <div data-navigation-item="" class="centered-nav__li">
            <a href="#" class="hamburger-nav__a">
              <p class="hamburger-nav__p">About us</p>
            </a>
          </div>
          <div data-navigation-item="" class="centered-nav__li">
            <a href="#" class="hamburger-nav__a">
              <p class="hamburger-nav__p">Our services</p>
            </a>
          </div>
          <div data-navigation-item="" class="centered-nav__li">
            <a href="#" class="hamburger-nav__a">
              <p class="hamburger-nav__p">Approach</p>
            </a>
          </div>
        </ul>
        <div data-navigation-item="" class="centered-nav__banner-w">
          <a href="#" class="centered-nav__banner w-inline-block">
            <div class="centered-nav__banner-row">
              <div data-css-marquee-list="" class="centered-nav__banner-item">
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
              </div>
              <div data-css-marquee-list="" class="centered-nav__banner-item">
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
                <div class="centered-nav__banner-inner"><p class="centered-nav__banner-text">Contact us</p></div>
              </div>
            </div>
          </a>
        </div>
      </div>
    </div>
  </div>
</nav>
CSS
css
.navigation {
  z-index: 500;
  pointer-events: none;
  position: fixed;
  inset: 0;
}

.navigation__dark-bg {
  opacity: 0;
  pointer-events: auto;
  visibility: hidden;
  background-color: #000;
  position: absolute;
  inset: 0;
  transition: all 0.7s cubic-bezier(0.5, 0.5, 0, 1);
}

[data-navigation-status="active"] .navigation__dark-bg {
  opacity: 0.15;
  visibility: visible;
}

.centered-nav {
  border-radius: .75em;
  flex-flow: column;
  justify-content: flex-start;
  align-items: stretch;
  width: 30em;
  display: flex;
  position: absolute;
  top: 2em;
  left: 50%;
  transform: translate(-50%);
}

.centered-nav__bg {
  background-color: #fff;
  border-radius: .75em;
  width: 100%;
  height: 100%;
  position: absolute;
  inset: 0%;
}

.centered-nav__header {
  z-index: 1;
  justify-content: space-between;
  align-items: center;
  padding: 1.25em 1.5em 1.25em 1.625em;
  display: flex;
  position: relative;
}

.centered-nav__logo {
  pointer-events: auto;
  color: #131313;
  justify-content: center;
  align-items: center;
  width: 7.5em;
  display: flex;
}

.centered-nav__logo-svg {
  width: 100%;
  height: 100%;
}

.centered-nav__toggle {
  pointer-events: auto;
  cursor: pointer;
  background-color: #0000;
  justify-content: center;
  align-items: center;
  width: 2.5em;
  height: 2.5em;
  padding: 0;
  display: flex;
  position: relative;
}

.centered-nav__toggle .centered-nav__toggle-bar {
  background-color: #131313;
  width: 1.875em;
  height: .125em;
  position: absolute;
  transition: transform 0.6s cubic-bezier(.65, 0, 0, 1);
  transform: translateY(-0.25em) rotate(0.001deg);
}

.centered-nav__toggle:hover .centered-nav__toggle-bar {
  transform: translateY(0.25em) rotate(0.001deg);
}

.centered-nav__toggle .centered-nav__toggle-bar:nth-child(2) {
  transform: translateY(0.15em) rotate(0.001deg);
}

.centered-nav__toggle:hover .centered-nav__toggle-bar:nth-child(2) {
  transform: translateY(-0.15em) rotate(0.001deg);
}

[data-navigation-status="active"] .centered-nav__toggle .centered-nav__toggle-bar {
  transform: translateY(0em) rotate(45deg);
}

[data-navigation-status="active"] .centered-nav__toggle .centered-nav__toggle-bar:nth-child(2) {
  transform: translateY(0em) rotate(-45deg);
}

.centered-nav__content {
  border-bottom-right-radius: .75em;
  border-bottom-left-radius: .75em;
  grid-template-rows: 0fr;
  display: grid;
  position: relative;
  overflow: hidden;
  transition: grid-template-rows 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}

[data-navigation-status="active"] .centered-nav__content {
  grid-template-rows: 1fr;
}

.centered-nav__inner {
  grid-column-gap: 3em;
  grid-row-gap: 3em;
  pointer-events: auto;
  flex-flow: column;
  justify-content: flex-start;
  align-items: center;
  width: 100%;
  height: 10000%;
  display: flex;
  position: relative;
  overflow: hidden;
}

.centered-nav__ul {
  flex-flow: column;
  justify-content: flex-start;
  align-items: stretch;
  width: 100%;
  margin-top: 0;
  margin-bottom: 0;
  padding: 0;
  display: flex;
  position: relative;
}

.centered-nav__li {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: clip;
}

.hamburger-nav__a::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  opacity: 0.2;
  transition-delay: inherit;
  transform: scaleX(0);
  transition: transform 0.6s cubic-bezier(.65, 0, 0, 1);
}

[data-navigation-status="active"] .hamburger-nav__a::after {
  transform: scaleX(1);
}

.hamburger-nav__a:hover::after {
  opacity: 0.4;
}

.hamburger-nav__a[aria-current]::after {
  opacity: 1;
}

.hamburger-nav__p {
  transition: transform 0.6s cubic-bezier(.65, 0, 0, 1);
  transform: translate(0px, 150%);
  transition-delay: inherit;
}

[data-navigation-status="active"] .hamburger-nav__p {
  transform: translate(0px, 0%);
}

.centered-nav__banner-w {
  justify-content: center;
  align-items: center;
  width: 100%;
  display: flex;
}

.centered-nav__banner {
  color: #fff;
  background-color: #ff4c24;
  max-width: 100%;
  padding-top: 1em;
  padding-bottom: 1em;
  text-decoration: none;
  transition: background-color .15s;
  overflow: hidden;
}

.centered-nav__banner:hover {
  background-color: #ff4c24e6;
}

.centered-nav__banner-row {
  justify-content: flex-start;
  align-items: center;
  display: flex;
}

.centered-nav__banner-item {
  flex: none;
  justify-content: flex-start;
  align-items: center;
  display: flex;
}

.centered-nav__banner-inner {
  flex: none;
  justify-content: flex-start;
  align-items: center;
  padding-right: 2em;
  display: flex;
}

.centered-nav__banner-text {
  text-transform: uppercase;
  margin-bottom: 0;
  font-family: RM Mono, Arial, sans-serif;
  font-size: .875em;
  font-weight: 400;
  line-height: 1;
}

@media screen and (max-width: 767px) {
  .centered-nav {
    width: auto;
    top: 1em;
    left: 1em;
    right: 1em;
    transform: none;
  }
}

@keyframes translateX {
  to {
    transform: translateX(-100%);
  }
}

[data-css-marquee-list] {
  animation: translateX 20s linear;
  animation-iteration-count: infinite;
  animation-play-state: paused;
}

[data-navigation-status="active"] [data-css-marquee-list] {
  animation-play-state: running;
}

[data-navigation-status="active"] .centered-nav__banner:hover [data-css-marquee-list] {
  animation-play-state: paused;
}
JavaScript
javascript
function initCenteredScalingNavigationBar() {
  const navigationInnerItems = document.querySelectorAll("[data-navigation-item]");

  // Apply staggered transition delays
  navigationInnerItems.forEach((item, index) => {
    item.style.transitionDelay = `${index * 0.05}s`;
  });

  // Toggle Navigation
  document.querySelectorAll('[data-navigation-toggle="toggle"]').forEach(toggleBtn => {
    toggleBtn.addEventListener('click', () => {
      const navStatusEl = document.querySelector('[data-navigation-status]');
      if (!navStatusEl) return;
      if (navStatusEl.getAttribute('data-navigation-status') === 'not-active') {
        navStatusEl.setAttribute('data-navigation-status', 'active');
        // If you use Lenis you can 'stop' Lenis here: Example Lenis.stop();
      } else {
        navStatusEl.setAttribute('data-navigation-status', 'not-active');
        // If you use Lenis you can 'start' Lenis here: Example Lenis.start();
      }
    });
  });

  // Close Navigation
  document.querySelectorAll('[data-navigation-toggle="close"]').forEach(closeBtn => {
    closeBtn.addEventListener('click', () => {
      const navStatusEl = document.querySelector('[data-navigation-status]');
      if (!navStatusEl) return;
      navStatusEl.setAttribute('data-navigation-status', 'not-active');
      // If you use Lenis you can 'start' Lenis here: Example Lenis.start();
    });
  });

  // ESC closes
  document.addEventListener('keydown', e => {
    if (e.keyCode === 27) {
      const navStatusEl = document.querySelector('[data-navigation-status]');
      if (!navStatusEl) return;
      if (navStatusEl.getAttribute('data-navigation-status') === 'active') {
        navStatusEl.setAttribute('data-navigation-status', 'not-active');
        // If you use Lenis you can 'start' Lenis here: Example Lenis.start();
      }
    }
  });
}

document.addEventListener('DOMContentLoaded', function() {
  initCenteredScalingNavigationBar();
});

Attributes

NameTypeDefaultDescription
data-navigation-status"active" | "not-active""not-active"Placed on the <nav> element (or <body> for broader scope). Flipping to "active" triggers the pill expansion, link slide-up, and marquee animation.
data-navigation-toggle="toggle"attributeAttach to the hamburger button. Toggles data-navigation-status between active and not-active on click.
data-navigation-toggle="close"attributeAttach to any element that should always close the nav, such as the dark overlay. The dark overlay (.navigation__dark-bg) has this by default.
data-navigation-itemattributeAttach to each list item or content block inside the nav. The JS assigns incrementing transition-delay values (0.05s per item) to create the staggered slide-up effect.
data-css-marquee-listattributeAttach to each duplicate marquee row. The CSS animation (translateX -100%) is paused by default and starts running when data-navigation-status is active. Hovering the banner pauses it again.

Notes

  • No GSAP required — all animation is CSS-driven.
  • The dark background overlay (.navigation__dark-bg) doubles as a close trigger via data-navigation-toggle="close".
  • Pressing Escape closes the nav via the keydown listener.
  • Staggered delays are applied inline by JS — no hard-coded nth-child rules needed in CSS.
  • To integrate Lenis, uncomment Lenis.stop() / Lenis.start() inside the toggle and close handlers.

Guide

Pill expansion

The nav content area uses grid-template-rows: 0fr → 1fr to expand downward without knowing the content height. The inner element has height: 10000% and overflow: hidden so the grid can clip it cleanly.

Staggered link reveal

Each [data-navigation-item] element gets an inline transition-delay set by JS (index × 0.05s). The CSS transitions on .hamburger-nav__p (translateY 150% → 0%) and the ::after underline both inherit this delay via transition-delay: inherit.

CSS marquee banner

Two duplicate .centered-nav__banner-item rows are placed side-by-side. Each animates translateX(-100%) on an infinite loop. The animation is paused when the nav is closed and resumes on open. Hovering the banner pauses playback.

Pill width

The default width is 30em on .centered-nav. On screens ≤767px the pill becomes full-width (left: 1em, right: 1em, transform: none). Adjust these values to taste.