Back to CSS Neon Neon Navigation CSS + JS
Share
HTML
<section class="nn-nav" aria-label="Neon navigation demo">
  <div class="stage">

    <nav class="nav-slide" data-nn-nav data-nav-kind="slide" aria-label="Main navigation">
      <span class="indicator" data-nav-indicator aria-hidden="true"></span>
      <a href="#" class="active" data-nav-item>Dashboard</a>
      <a href="#" data-nav-item>Analytics</a>
      <a href="#" data-nav-item>Systems</a>
      <a href="#" data-nav-item>Network</a>
      <a href="#" data-nav-item>Config</a>
    </nav>

    <nav class="nav-pills" data-nn-nav data-nav-kind="pill" aria-label="Section navigation">
      <span class="pill-bg" data-nav-pill aria-hidden="true"></span>
      <a href="#" class="active" data-nav-item>Recon</a>
      <a href="#" data-nav-item>Arsenal</a>
      <a href="#" data-nav-item>Comms</a>
      <a href="#" data-nav-item>Intel</a>
    </nav>

    <nav class="nav-sidebar" data-nn-nav data-nav-kind="side" aria-label="Sidebar navigation">
      <span class="side-indicator" data-nav-indicator aria-hidden="true"></span>
      <span class="row-bg" data-nav-rowbg aria-hidden="true"></span>
      <a href="#" class="active" data-nav-item>
        <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
        Overview
      </a>
      <a href="#" data-nav-item>
        <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
        Vitals
      </a>
      <a href="#" data-nav-item>
        <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/></svg>
        Signal
      </a>
      <a href="#" data-nav-item>
        <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
        Security
      </a>
      <a href="#" data-nav-item>
        <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
        Alerts
      </a>
    </nav>

  </div>
</section>
CSS
/* ─── 06 Neon Navigation — slide / pill / sidebar trio ─────────── */
@import url('https://fonts.googleapis.com/css2?family=Syncopate:wght@400;700&family=Jost:wght@300;400;600&display=swap');

.nn-nav {
  position: relative;
  width: 100%;
  min-height: 660px;
  background: #040410;
  font-family: 'Jost', sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 56px 16px;
  overflow: hidden;
  box-sizing: border-box;
}

.nn-nav *,
.nn-nav *::before,
.nn-nav *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Subtle blue grid — was body::before fixed; scoped. */
.nn-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(0,200,255,0.025) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0,200,255,0.025) 1px, transparent 1px);
  background-size: 50px 50px;
  pointer-events: none;
  z-index: 0;
}

.nn-nav .stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 56px;
}

/* NAV 1 — SLIDING UNDERLINE */
.nn-nav .nav-slide {
  position: relative;
  display: flex;
  gap: 0;
  background: rgba(8,8,22,0.9);
  border: 1px solid rgba(0,230,255,0.1);
  padding: 0 4px;
  box-shadow: 0 0 30px rgba(0,0,0,0.6), inset 0 0 30px rgba(0,0,0,0.4);
}
.nn-nav .nav-slide a {
  position: relative;
  padding: 18px 28px;
  font-family: 'Syncopate', sans-serif;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(150,180,220,0.55);
  transition: color 0.3s ease;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.nn-nav .nav-slide a:hover { color: rgba(200,230,255,0.85); }
.nn-nav .nav-slide a.active {
  color: #00e6ff;
  text-shadow: 0 0 8px rgba(0,230,255,0.8), 0 0 20px rgba(0,230,255,0.4);
}
.nn-nav .nav-slide .indicator {
  position: absolute;
  bottom: 0;
  height: 2px;
  background: #00e6ff;
  box-shadow: 0 0 8px #00e6ff, 0 0 20px rgba(0,230,255,0.6), 0 0 40px rgba(0,230,255,0.3);
  transition: left 0.35s cubic-bezier(0.23, 1, 0.32, 1), width 0.35s cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
}
.nn-nav .nav-slide::before,
.nn-nav .nav-slide::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  border-color: rgba(0,230,255,0.5);
  border-style: solid;
}
.nn-nav .nav-slide::before { top: -1px; left: -1px; border-width: 1.5px 0 0 1.5px; }
.nn-nav .nav-slide::after  { bottom: -1px; right: -1px; border-width: 0 1.5px 1.5px 0; }

/* NAV 2 — PILL */
.nn-nav .nav-pills {
  position: relative;
  display: flex;
  gap: 8px;
  background: rgba(6,6,18,0.95);
  border: 1px solid rgba(191,95,255,0.15);
  border-radius: 50px;
  padding: 6px;
  box-shadow: 0 0 40px rgba(0,0,0,0.5), inset 0 0 20px rgba(0,0,0,0.3);
}
.nn-nav .nav-pills a {
  position: relative;
  padding: 11px 26px;
  border-radius: 50px;
  font-family: 'Syncopate', sans-serif;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(170,150,210,0.55);
  transition: all 0.3s ease;
  cursor: pointer;
  user-select: none;
  z-index: 1;
}
.nn-nav .nav-pills a:hover { color: rgba(210,180,255,0.85); }
.nn-nav .nav-pills a.active {
  color: #fff;
  text-shadow: 0 0 6px rgba(191,95,255,0.9);
}
.nn-nav .nav-pills .pill-bg {
  position: absolute;
  border-radius: 50px;
  background: rgba(191,95,255,0.18);
  border: 1px solid rgba(191,95,255,0.5);
  box-shadow: 0 0 14px rgba(191,95,255,0.35), inset 0 0 10px rgba(191,95,255,0.07);
  transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
  z-index: 0;
}

/* NAV 3 — SIDEBAR */
.nn-nav .nav-sidebar {
  position: relative;
  display: flex;
  flex-direction: column;
  background: rgba(6,6,20,0.9);
  border: 1px solid rgba(255,45,120,0.12);
  width: 220px;
  padding: 6px;
  box-shadow: 0 0 40px rgba(0,0,0,0.5), 4px 0 30px rgba(255,45,120,0.05);
}
.nn-nav .nav-sidebar a {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  font-family: 'Jost', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(180,150,180,0.5);
  transition: all 0.25s ease;
  cursor: pointer;
  user-select: none;
  position: relative;
  z-index: 1;
}
.nn-nav .nav-sidebar a:hover { color: rgba(255,180,210,0.85); }
.nn-nav .nav-sidebar a.active {
  color: #ff2d78;
  text-shadow: 0 0 8px rgba(255,45,120,0.7);
}
.nn-nav .nav-sidebar a .icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.7;
  transition: opacity 0.25s;
}
.nn-nav .nav-sidebar a.active .icon {
  opacity: 1;
  filter: drop-shadow(0 0 4px rgba(255,45,120,0.6));
}
.nn-nav .nav-sidebar .side-indicator {
  position: absolute;
  left: 0;
  width: 2px;
  background: #ff2d78;
  box-shadow: 0 0 8px #ff2d78, 0 0 20px rgba(255,45,120,0.5);
  transition: top 0.35s cubic-bezier(0.23, 1, 0.32, 1), height 0.35s cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
}
.nn-nav .nav-sidebar .row-bg {
  position: absolute;
  left: 0;
  right: 0;
  background: rgba(255,45,120,0.07);
  border-right: 1px solid rgba(255,45,120,0.2);
  transition: top 0.35s cubic-bezier(0.23, 1, 0.32, 1), height 0.35s cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
  z-index: 0;
}

@media (max-width: 600px) {
  .nn-nav .nav-slide { flex-wrap: wrap; }
}

@media (prefers-reduced-motion: reduce) {
  .nn-nav .nav-slide .indicator,
  .nn-nav .nav-pills .pill-bg,
  .nn-nav .nav-sidebar .side-indicator,
  .nn-nav .nav-sidebar .row-bg { transition: none !important; }
}
JS
(() => {
  // Scoped nav delegator — replaces the source mock's three globally
  // referenced setActive/setActivePill/setActiveSide functions with a
  // single delegator that derives state from the clicked link's
  // nearest [data-nn-nav] container. Multiple instances coexist with
  // no global handlers or element-id collisions.
  const root = document.querySelector('.nn-nav');
  if (!root) return;

  function paint(nav) {
    const kind = nav.dataset.navKind;
    const active = nav.querySelector('a.active') || nav.querySelector('a');
    if (!active) return;

    if (kind === 'slide') {
      const ind = nav.querySelector('[data-nav-indicator]');
      if (!ind) return;
      ind.style.left  = active.offsetLeft + 'px';
      ind.style.width = active.offsetWidth + 'px';
    } else if (kind === 'pill') {
      const pill = nav.querySelector('[data-nav-pill]');
      if (!pill) return;
      pill.style.left   = active.offsetLeft   + 'px';
      pill.style.top    = active.offsetTop    + 'px';
      pill.style.width  = active.offsetWidth  + 'px';
      pill.style.height = active.offsetHeight + 'px';
    } else if (kind === 'side') {
      const ind = nav.querySelector('[data-nav-indicator]');
      const bg  = nav.querySelector('[data-nav-rowbg]');
      if (ind) { ind.style.top = active.offsetTop + 'px'; ind.style.height = active.offsetHeight + 'px'; }
      if (bg)  { bg.style.top  = active.offsetTop + 'px'; bg.style.height  = active.offsetHeight + 'px'; }
    }
  }

  function paintAll() {
    root.querySelectorAll('[data-nn-nav]').forEach(paint);
  }

  // Initial paint — need layout to be settled, so do a microtask + rAF.
  paintAll();
  requestAnimationFrame(paintAll);

  root.addEventListener('click', (e) => {
    const link = e.target.closest('[data-nav-item]');
    if (!link) return;
    e.preventDefault();
    const nav = link.closest('[data-nn-nav]');
    if (!nav) return;
    nav.querySelectorAll('a').forEach(a => a.classList.remove('active'));
    link.classList.add('active');
    paint(nav);
  });

  // Repaint on resize (offsets can change with breakpoint shifts).
  let resizeTimer;
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(paintAll, 100);
  });
})();