8 CSS Navbar Designs05 / 08

CSS + JSMIT licensed

Sticky CSS Navbar on Scroll

A sustainable lifestyle brand navbar UI that gracefully compresses on scroll — proving the right way to do scroll-transforming sticky navigation without janky reflow. The navbar starts at 96px tall with a large Fraunces serif "Sylvé" wordmark and a "Sustainable Living" sub-tagline beneath; as the page scrolls past 60px, a single class toggle (driven by IntersectionObserver scoped to this demo) compresses the bar to a 60px frosted-glass version, fades the sub-tagline out, and shrinks the wordmark to 1.3rem — all via pure CSS transitions, no JS animation loop. A 3px lime-to-clay gradient progress bar tracks reading depth at the very top of the wrapper. Below the navbar is a sage-and-sand long-form page: decorative circles in the hero, Fraunces italic "earth in mind" headline, a 3-up Regenerative/Zero-Waste/Transparent feature grid, a sage pull-quote divider, and a "Slow Living" journal section that gives readers reason to scroll. Fraunces + Mulish. Best for sustainable brands, lifestyle e-commerce, editorial product sites, slow-content brands, marketing pages that need scroll-aware chrome.

Published Updated

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<section class="nb-stk" aria-label="Sylve sticky scroll navbar demo">
  <div class="progress-bar" data-nb-stk-progress aria-hidden="true"></div>

  <nav data-nb-stk-nav aria-label="Primary">
    <div class="nav-inner">
      <a href="#nb-stk-home" class="brand">
        <span class="brand-main">Sylvé</span>
        <span class="brand-sub">Sustainable Living</span>
      </a>
      <ul class="nav-links">
        <li><a href="#nb-stk-home" class="active">Home</a></li>
        <li><a href="#nb-stk-about">About</a></li>
        <li><a href="#nb-stk-products">Products</a></li>
        <li><a href="#nb-stk-journal">Journal</a></li>
        <li><a href="#nb-stk-contact">Contact</a></li>
      </ul>
      <div class="nav-actions">
        <button class="btn-ghost" type="button">Log in</button>
        <button class="btn-solid" type="button">Shop Now</button>
      </div>
    </div>
  </nav>

  <section class="hero" id="nb-stk-home">
    <div class="hero-circle hc-1" aria-hidden="true"></div>
    <div class="hero-circle hc-2" aria-hidden="true"></div>
    <div class="hero-circle hc-3" aria-hidden="true"></div>
    <div class="hero-circle hc-4" aria-hidden="true"></div>
    <div class="hero-inner">
      <p class="hero-eyebrow">New Collection 2026</p>
      <h1>Living in<br>full <em>bloom</em></h1>
      <p class="hero-body">Scroll down to watch the navbar gracefully compress and transform — growing more refined as you descend deeper into the page.</p>
      <div class="hero-ctas">
        <button class="cta-primary" type="button">Explore Collection</button>
        <a href="#nb-stk-about" class="cta-secondary">
          Learn more
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
        </a>
      </div>
    </div>
    <div class="scroll-hint" aria-hidden="true">
      <div class="scroll-mouse"></div>
      <span class="scroll-hint-text">Scroll to see nav transform</span>
    </div>
  </section>

  <section class="section" id="nb-stk-about">
    <div class="section-inner">
      <p class="section-label">Our Philosophy</p>
      <h2 class="section-title">Designed with the<br><em>earth in mind</em></h2>
      <p class="section-body">Every product in our collection is crafted with deep respect for natural systems. We source exclusively from regenerative farms and artisan workshops that prioritize ecological balance over extraction.</p>
      <div class="feature-grid">
        <div class="feature-card">
          <div class="feature-num">01</div>
          <div class="feature-title">Regenerative</div>
          <div class="feature-text">All materials sourced from certified regenerative agriculture operations that actively restore soil biodiversity.</div>
        </div>
        <div class="feature-card">
          <div class="feature-num">02</div>
          <div class="feature-title">Zero-Waste</div>
          <div class="feature-text">Our production process generates zero landfill waste. All offcuts are composted or repurposed into secondary goods.</div>
        </div>
        <div class="feature-card">
          <div class="feature-num">03</div>
          <div class="feature-title">Transparent</div>
          <div class="feature-text">Every product includes a full supply chain trace, from seed to shelf, available in your account dashboard.</div>
        </div>
      </div>
    </div>
  </section>

  <div class="divider-section" id="nb-stk-products">
    <div class="section-inner divider-inner">
      <h2>"The most sustainable product<br>is one that lasts a lifetime."</h2>
      <button type="button">Browse All Products →</button>
    </div>
  </div>

  <section class="section long-section" id="nb-stk-journal">
    <div class="section-inner">
      <p class="section-label">From the Journal</p>
      <h2 class="section-title">Stories of<br><em>slow living</em></h2>
      <p class="section-body">Reflections on seasonality, craft, ecology, and the small daily choices that accumulate into a life well-lived in relationship with the living world.</p>
    </div>
  </section>

  <section class="section long-section" id="nb-stk-contact">
    <div class="section-inner">
      <p class="section-label">Get in Touch</p>
      <h2 class="section-title">Let's grow<br><em>together</em></h2>
      <p class="section-body">Whether you're a stockist, collaborator, or simply curious — we'd love to hear from you. Reach us at hello@sylve.co</p>
    </div>
  </section>
</section>
/* ─── 05 Sylvé — sticky scroll-transforming navbar UI ──────────── */

@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,900;1,9..144,300;1,9..144,600&family=Mulish:wght@300;400;600&display=swap');

.nb-stk {
  --nb-stk-sage: #3a5a40;
  --nb-stk-sage-light: #4e7c57;
  --nb-stk-sand: #f7f4ef;
  --nb-stk-warm: #e8e0d0;
  --nb-stk-warm-2: #d4c9b4;
  --nb-stk-clay: #8b6f47;
  --nb-stk-text: #1c2417;
  --nb-stk-muted: #7a8c72;
  --nb-stk-nav-h-tall: 96px;
  --nb-stk-nav-h-short: 60px;
  position: relative;
  width: 100%;
  min-height: 1800px;
  background: var(--nb-stk-sand);
  color: var(--nb-stk-text);
  font-family: 'Mulish', sans-serif;
  /* overflow:clip keeps horizontal escapes contained without
       establishing a scrolling container — overflow:hidden would make
       this wrapper the scrolling ancestor for the sticky nav and break
       sticky behavior inside the iframe + gallery context. */
  overflow: clip;
  box-sizing: border-box;
  scroll-behavior: smooth;
}

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

.nb-stk .progress-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--nb-stk-sage), var(--nb-stk-clay));
  z-index: 200;
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 0.05s linear;
  border-radius: 0 2px 2px 0;
  width: 100%;
}

.nb-stk nav {
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nb-stk-nav-h-tall);
  background: transparent;
  transition: height 0.4s cubic-bezier(0.4,0,0.2,1), background 0.4s ease, box-shadow 0.4s ease, border-bottom 0.4s ease;
}

.nb-stk nav.scrolled {
  height: var(--nb-stk-nav-h-short);
  background: rgba(247,244,239,0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 1px 0 rgba(58,90,64,0.1), 0 8px 32px rgba(28,36,23,0.06);
}

.nb-stk .nav-inner {
  max-width: 1320px;
  margin: 0 auto;
  height: 100%;
  display: flex;
  align-items: center;
  padding: 0 3rem;
  gap: 2rem;
  transition: padding 0.4s cubic-bezier(0.4,0,0.2,1);
}

.nb-stk nav.scrolled .nav-inner {
  padding: 0 2rem;
}

.nb-stk .brand {
  text-decoration: none;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.nb-stk .brand-main {
  font-family: 'Fraunces', serif;
  font-size: 2rem;
  font-weight: 900;
  letter-spacing: -0.04em;
  color: var(--nb-stk-sage);
  line-height: 1;
  transition: font-size 0.4s cubic-bezier(0.4,0,0.2,1);
}

.nb-stk .brand-sub {
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nb-stk-muted);
  line-height: 1;
  margin-top: 3px;
  transition: opacity 0.3s, max-height 0.3s, margin 0.3s;
  max-height: 20px;
  overflow: hidden;
}

.nb-stk nav.scrolled .brand-main {
  font-size: 1.3rem;
}

.nb-stk nav.scrolled .brand-sub {
  opacity: 0;
  max-height: 0;
  margin: 0;
}

.nb-stk .nav-links {
  display: flex;
  align-items: center;
  list-style: none;
  gap: 0.25rem;
  flex: 1;
  justify-content: center;
}

.nb-stk .nav-links a {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-muted);
  text-decoration: none;
  padding: 8px 14px;
  border-radius: 100px;
  transition: color 0.2s, background 0.2s;
  position: relative;
}

.nb-stk .nav-links a:hover {
  color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.06);
}

.nb-stk .nav-links a.active {
  color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.1);
}

.nb-stk .nav-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}

.nb-stk .btn-ghost {
  font-family: 'Mulish', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-sage);
  background: none;
  border: 1.5px solid var(--nb-stk-warm-2);
  border-radius: 100px;
  padding: 8px 18px;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}

.nb-stk .btn-ghost:hover {
  border-color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.04);
}

.nb-stk .btn-solid {
  font-family: 'Mulish', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-sand);
  background: var(--nb-stk-sage);
  border: 1.5px solid var(--nb-stk-sage);
  border-radius: 100px;
  padding: 8px 20px;
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
}

.nb-stk .btn-solid:hover {
  background: var(--nb-stk-sage-light);
  transform: translateY(-1px);
}

.nb-stk .hero {
  min-height: 680px;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  padding-top: 1rem;
}

.nb-stk .hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 70% 40%, rgba(58,90,64,0.06) 0%, transparent 60%), radial-gradient(ellipse at 20% 80%, rgba(139,111,71,0.06) 0%, transparent 50%);
}

.nb-stk .hero-circle {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.nb-stk .hc-1 {
  width: 500px;
  height: 500px;
  border: 1px solid rgba(58,90,64,0.1);
  top: 10%;
  right: -100px;
}

.nb-stk .hc-2 {
  width: 300px;
  height: 300px;
  border: 1px solid rgba(58,90,64,0.08);
  top: 25%;
  right: 50px;
}

.nb-stk .hc-3 {
  width: 40px;
  height: 40px;
  background: rgba(58,90,64,0.12);
  bottom: 30%;
  left: 10%;
  animation: nb-stk-float 8s ease-in-out infinite;
}

.nb-stk .hc-4 {
  width: 16px;
  height: 16px;
  background: var(--nb-stk-clay);
  opacity: 0.3;
  top: 35%;
  left: 25%;
  animation: nb-stk-float 12s ease-in-out infinite reverse;
  border-radius: 50%;
}

@keyframes nb-stk-float {
  0%,
    100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

.nb-stk .hero-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 4rem 3rem;
  position: relative;
  z-index: 1;
  width: 100%;
}

.nb-stk .hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--nb-stk-clay);
  margin-bottom: 2rem;
}

.nb-stk .hero-eyebrow::before {
  content: '';
  width: 28px;
  height: 1.5px;
  background: var(--nb-stk-clay);
}

.nb-stk h1 {
  font-family: 'Fraunces', serif;
  font-size: clamp(4rem, 7vw, 8rem);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--nb-stk-text);
  margin-bottom: 2rem;
  max-width: 14ch;
}

.nb-stk h1 em {
  font-style: italic;
  color: var(--nb-stk-sage);
}

.nb-stk .hero-body {
  font-size: 1.05rem;
  color: var(--nb-stk-muted);
  line-height: 1.7;
  max-width: 42ch;
  margin-bottom: 2.5rem;
}

.nb-stk .hero-ctas {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.nb-stk .cta-primary {
  font-family: 'Mulish', sans-serif;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--nb-stk-sand);
  background: var(--nb-stk-sage);
  border: none;
  border-radius: 100px;
  padding: 14px 32px;
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
  box-shadow: 0 4px 24px rgba(58,90,64,0.3);
}

.nb-stk .cta-primary:hover {
  background: var(--nb-stk-sage-light);
  transform: translateY(-2px);
}

.nb-stk .cta-secondary {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--nb-stk-muted);
  text-decoration: none;
  transition: color 0.2s, gap 0.2s;
}

.nb-stk .cta-secondary:hover {
  color: var(--nb-stk-sage);
  gap: 12px;
}

.nb-stk .section {
  padding: 8rem 0;
}

.nb-stk .section-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 3rem;
}

.nb-stk .section-label {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--nb-stk-clay);
  margin-bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 10px;
}

.nb-stk .section-label::after {
  content: '';
  flex: 1;
  max-width: 40px;
  height: 1px;
  background: var(--nb-stk-clay);
}

.nb-stk .section-title {
  font-family: 'Fraunces', serif;
  font-size: clamp(2.5rem, 4vw, 4rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  color: var(--nb-stk-text);
  line-height: 1.1;
  margin-bottom: 1.5rem;
}

.nb-stk .section-title em {
  font-style: italic;
  color: var(--nb-stk-sage);
}

.nb-stk .section-body {
  font-size: 1rem;
  color: var(--nb-stk-muted);
  max-width: 52ch;
  line-height: 1.75;
}

.nb-stk .feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5px;
  margin-top: 4rem;
  background: var(--nb-stk-warm-2);
  border: 1.5px solid var(--nb-stk-warm-2);
}

.nb-stk .feature-card {
  background: var(--nb-stk-sand);
  padding: 2.5rem 2rem;
  transition: background 0.2s;
}

.nb-stk .feature-card:hover {
  background: var(--nb-stk-warm);
}

.nb-stk .feature-num {
  font-family: 'Fraunces', serif;
  font-size: 2.5rem;
  font-weight: 900;
  color: rgba(58,90,64,0.12);
  line-height: 1;
  margin-bottom: 1rem;
}

.nb-stk .feature-title {
  font-family: 'Fraunces', serif;
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--nb-stk-text);
  margin-bottom: 0.75rem;
}

.nb-stk .feature-text {
  font-size: 0.85rem;
  color: var(--nb-stk-muted);
  line-height: 1.65;
}

.nb-stk .divider-section {
  background: var(--nb-stk-sage);
  padding: 5rem 0;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.nb-stk .divider-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(45deg, transparent, transparent 40px, rgba(255,255,255,0.02) 40px, rgba(255,255,255,0.02) 41px);
}

.nb-stk .divider-inner {
  position: relative;
  z-index: 1;
}

.nb-stk .divider-inner h2 {
  font-family: 'Fraunces', serif;
  font-size: clamp(2rem, 4vw, 4rem);
  font-weight: 300;
  font-style: italic;
  color: rgba(247,244,239,0.95);
  line-height: 1.2;
  margin-bottom: 2rem;
}

.nb-stk .divider-inner button {
  font-family: 'Mulish', sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nb-stk-sage);
  background: var(--nb-stk-sand);
  border: none;
  border-radius: 100px;
  padding: 13px 30px;
  cursor: pointer;
}

.nb-stk .long-section {
  padding: 6rem 0;
  border-top: 1px solid var(--nb-stk-warm-2);
}

.nb-stk .scroll-hint {
  position: absolute;
  bottom: 3rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--nb-stk-muted);
  z-index: 1;
}

.nb-stk .scroll-hint-text {
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.nb-stk .scroll-mouse {
  width: 22px;
  height: 34px;
  border: 1.5px solid var(--nb-stk-warm-2);
  border-radius: 12px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 6px;
}

.nb-stk .scroll-mouse::after {
  content: '';
  width: 4px;
  height: 8px;
  background: var(--nb-stk-sage);
  border-radius: 2px;
  animation: nb-stk-scrollDot 1.8s ease-in-out infinite;
}

@keyframes nb-stk-scrollDot {
  0%,
    100% {
    opacity: 1;
    transform: translateY(0);
  }

  50% {
    opacity: 0.2;
    transform: translateY(6px);
  }
}

@media (max-width: 768px) {
  .nb-stk .feature-grid {
    grid-template-columns: 1fr;
  }

  .nb-stk .hero-inner {
    padding: 3rem 1.5rem;
  }

  .nb-stk .section-inner {
    padding: 0 1.5rem;
  }

  .nb-stk .nav-links {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nb-stk nav,
    .nb-stk .brand-main,
    .nb-stk .brand-sub,
    .nb-stk .hc-3,
    .nb-stk .hc-4,
    .nb-stk .scroll-mouse::after {
    animation: none !important;
    transition: none !important;
  }
}
(() => {
  // Scoped sticky-scroll navbar wiring. The source mock bound a
  // `window.scroll` listener and read documentElement.scrollTop —
  // that would track the host gallery's scroll. Re-scope: use a
  // sentinel `hero` element to toggle the .scrolled class via
  // IntersectionObserver (fires when the hero top passes 60px below
  // the viewport top), and compute reading progress from the wrapper's
  // own bounding rect against the viewport.
  const root = document.querySelector('.nb-stk');
  if (!root) return;
  const nav = root.querySelector('[data-nb-stk-nav]');
  const progress = root.querySelector('[data-nb-stk-progress]');
  const hero = root.querySelector('.hero');
  if (!nav || !progress) return;

  // Toggle .scrolled when the hero crosses 60px below the viewport top.
  if (hero && 'IntersectionObserver' in window) {
    const io = new IntersectionObserver(
      ([entry]) => {
        // entry.boundingClientRect.top < -60 means the hero has scrolled
        // up past the threshold. We rely on entry.isIntersecting plus
        // the sentinel offset.
        const top = entry.boundingClientRect.top;
        nav.classList.toggle('scrolled', top < -60);
      },
      { threshold: [0], rootMargin: '-60px 0px 0px 0px' }
    );
    io.observe(hero);
  }

  function updateProgress() {
    const r = root.getBoundingClientRect();
    const vh = window.innerHeight;
    const total = root.offsetHeight - vh;
    if (total <= 0) { progress.style.transform = 'scaleX(0)'; return; }
    const scrolled = Math.min(Math.max(-r.top, 0), total);
    const pct = scrolled / total;
    progress.style.transform = 'scaleX(' + pct.toFixed(4) + ')';
  }
  window.addEventListener('scroll', updateProgress, { passive: true });
  window.addEventListener('resize', updateProgress);
  updateProgress();

  // Smooth scroll for in-page anchors.
  root.querySelectorAll('.nav-links a').forEach((a) => {
    a.addEventListener('click', (e) => {
      const href = a.getAttribute('href');
      if (!href || !href.startsWith('#')) return;
      const target = root.querySelector(href);
      if (!target) return;
      e.preventDefault();
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      root.querySelectorAll('.nav-links a').forEach((l) => l.classList.remove('active'));
      a.classList.add('active');
    });
  });
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Navbar Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Sticky CSS Navbar on Scroll
Source: https://codefronts.com/navigation/css-navbars/sticky-css-navbar-on-scroll/

A sustainable lifestyle brand navbar UI that gracefully compresses on scroll — proving the right way to do scroll-transforming sticky navigation without janky reflow. The navbar starts at 96px tall with a large Fraunces serif "Sylvé" wordmark and a "Sustainable Living" sub-tagline beneath; as the page scrolls past 60px, a single class toggle (driven by IntersectionObserver scoped to this demo) compresses the bar to a 60px frosted-glass version, fades the sub-tagline out, and shrinks the wordmark to 1.3rem — all via pure CSS transitions, no JS animation loop. A 3px lime-to-clay gradient progress bar tracks reading depth at the very top of the wrapper. Below the navbar is a sage-and-sand long-form page: decorative circles in the hero, Fraunces italic "earth in mind" headline, a 3-up Regenerative/Zero-Waste/Transparent feature grid, a sage pull-quote divider, and a "Slow Living" journal section that gives readers reason to scroll. Fraunces + Mulish. Best for sustainable brands, lifestyle e-commerce, editorial product sites, slow-content brands, marketing pages that need scroll-aware chrome.
## HTML
```html
<section class="nb-stk" aria-label="Sylve sticky scroll navbar demo">
  <div class="progress-bar" data-nb-stk-progress aria-hidden="true"></div>

  <nav data-nb-stk-nav aria-label="Primary">
    <div class="nav-inner">
      <a href="#nb-stk-home" class="brand">
        <span class="brand-main">Sylvé</span>
        <span class="brand-sub">Sustainable Living</span>
      </a>
      <ul class="nav-links">
        <li><a href="#nb-stk-home" class="active">Home</a></li>
        <li><a href="#nb-stk-about">About</a></li>
        <li><a href="#nb-stk-products">Products</a></li>
        <li><a href="#nb-stk-journal">Journal</a></li>
        <li><a href="#nb-stk-contact">Contact</a></li>
      </ul>
      <div class="nav-actions">
        <button class="btn-ghost" type="button">Log in</button>
        <button class="btn-solid" type="button">Shop Now</button>
      </div>
    </div>
  </nav>

  <section class="hero" id="nb-stk-home">
    <div class="hero-circle hc-1" aria-hidden="true"></div>
    <div class="hero-circle hc-2" aria-hidden="true"></div>
    <div class="hero-circle hc-3" aria-hidden="true"></div>
    <div class="hero-circle hc-4" aria-hidden="true"></div>
    <div class="hero-inner">
      <p class="hero-eyebrow">New Collection 2026</p>
      <h1>Living in<br>full <em>bloom</em></h1>
      <p class="hero-body">Scroll down to watch the navbar gracefully compress and transform — growing more refined as you descend deeper into the page.</p>
      <div class="hero-ctas">
        <button class="cta-primary" type="button">Explore Collection</button>
        <a href="#nb-stk-about" class="cta-secondary">
          Learn more
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
        </a>
      </div>
    </div>
    <div class="scroll-hint" aria-hidden="true">
      <div class="scroll-mouse"></div>
      <span class="scroll-hint-text">Scroll to see nav transform</span>
    </div>
  </section>

  <section class="section" id="nb-stk-about">
    <div class="section-inner">
      <p class="section-label">Our Philosophy</p>
      <h2 class="section-title">Designed with the<br><em>earth in mind</em></h2>
      <p class="section-body">Every product in our collection is crafted with deep respect for natural systems. We source exclusively from regenerative farms and artisan workshops that prioritize ecological balance over extraction.</p>
      <div class="feature-grid">
        <div class="feature-card">
          <div class="feature-num">01</div>
          <div class="feature-title">Regenerative</div>
          <div class="feature-text">All materials sourced from certified regenerative agriculture operations that actively restore soil biodiversity.</div>
        </div>
        <div class="feature-card">
          <div class="feature-num">02</div>
          <div class="feature-title">Zero-Waste</div>
          <div class="feature-text">Our production process generates zero landfill waste. All offcuts are composted or repurposed into secondary goods.</div>
        </div>
        <div class="feature-card">
          <div class="feature-num">03</div>
          <div class="feature-title">Transparent</div>
          <div class="feature-text">Every product includes a full supply chain trace, from seed to shelf, available in your account dashboard.</div>
        </div>
      </div>
    </div>
  </section>

  <div class="divider-section" id="nb-stk-products">
    <div class="section-inner divider-inner">
      <h2>"The most sustainable product<br>is one that lasts a lifetime."</h2>
      <button type="button">Browse All Products →</button>
    </div>
  </div>

  <section class="section long-section" id="nb-stk-journal">
    <div class="section-inner">
      <p class="section-label">From the Journal</p>
      <h2 class="section-title">Stories of<br><em>slow living</em></h2>
      <p class="section-body">Reflections on seasonality, craft, ecology, and the small daily choices that accumulate into a life well-lived in relationship with the living world.</p>
    </div>
  </section>

  <section class="section long-section" id="nb-stk-contact">
    <div class="section-inner">
      <p class="section-label">Get in Touch</p>
      <h2 class="section-title">Let's grow<br><em>together</em></h2>
      <p class="section-body">Whether you're a stockist, collaborator, or simply curious — we'd love to hear from you. Reach us at hello@sylve.co</p>
    </div>
  </section>
</section>
```
## CSS
```css
/* ─── 05 Sylvé — sticky scroll-transforming navbar UI ──────────── */

@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,900;1,9..144,300;1,9..144,600&family=Mulish:wght@300;400;600&display=swap');

.nb-stk {
  --nb-stk-sage: #3a5a40;
  --nb-stk-sage-light: #4e7c57;
  --nb-stk-sand: #f7f4ef;
  --nb-stk-warm: #e8e0d0;
  --nb-stk-warm-2: #d4c9b4;
  --nb-stk-clay: #8b6f47;
  --nb-stk-text: #1c2417;
  --nb-stk-muted: #7a8c72;
  --nb-stk-nav-h-tall: 96px;
  --nb-stk-nav-h-short: 60px;
  position: relative;
  width: 100%;
  min-height: 1800px;
  background: var(--nb-stk-sand);
  color: var(--nb-stk-text);
  font-family: 'Mulish', sans-serif;
  /* overflow:clip keeps horizontal escapes contained without
       establishing a scrolling container — overflow:hidden would make
       this wrapper the scrolling ancestor for the sticky nav and break
       sticky behavior inside the iframe + gallery context. */
  overflow: clip;
  box-sizing: border-box;
  scroll-behavior: smooth;
}

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

.nb-stk .progress-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--nb-stk-sage), var(--nb-stk-clay));
  z-index: 200;
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 0.05s linear;
  border-radius: 0 2px 2px 0;
  width: 100%;
}

.nb-stk nav {
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nb-stk-nav-h-tall);
  background: transparent;
  transition: height 0.4s cubic-bezier(0.4,0,0.2,1), background 0.4s ease, box-shadow 0.4s ease, border-bottom 0.4s ease;
}

.nb-stk nav.scrolled {
  height: var(--nb-stk-nav-h-short);
  background: rgba(247,244,239,0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 1px 0 rgba(58,90,64,0.1), 0 8px 32px rgba(28,36,23,0.06);
}

.nb-stk .nav-inner {
  max-width: 1320px;
  margin: 0 auto;
  height: 100%;
  display: flex;
  align-items: center;
  padding: 0 3rem;
  gap: 2rem;
  transition: padding 0.4s cubic-bezier(0.4,0,0.2,1);
}

.nb-stk nav.scrolled .nav-inner {
  padding: 0 2rem;
}

.nb-stk .brand {
  text-decoration: none;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.nb-stk .brand-main {
  font-family: 'Fraunces', serif;
  font-size: 2rem;
  font-weight: 900;
  letter-spacing: -0.04em;
  color: var(--nb-stk-sage);
  line-height: 1;
  transition: font-size 0.4s cubic-bezier(0.4,0,0.2,1);
}

.nb-stk .brand-sub {
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nb-stk-muted);
  line-height: 1;
  margin-top: 3px;
  transition: opacity 0.3s, max-height 0.3s, margin 0.3s;
  max-height: 20px;
  overflow: hidden;
}

.nb-stk nav.scrolled .brand-main {
  font-size: 1.3rem;
}

.nb-stk nav.scrolled .brand-sub {
  opacity: 0;
  max-height: 0;
  margin: 0;
}

.nb-stk .nav-links {
  display: flex;
  align-items: center;
  list-style: none;
  gap: 0.25rem;
  flex: 1;
  justify-content: center;
}

.nb-stk .nav-links a {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-muted);
  text-decoration: none;
  padding: 8px 14px;
  border-radius: 100px;
  transition: color 0.2s, background 0.2s;
  position: relative;
}

.nb-stk .nav-links a:hover {
  color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.06);
}

.nb-stk .nav-links a.active {
  color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.1);
}

.nb-stk .nav-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}

.nb-stk .btn-ghost {
  font-family: 'Mulish', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-sage);
  background: none;
  border: 1.5px solid var(--nb-stk-warm-2);
  border-radius: 100px;
  padding: 8px 18px;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}

.nb-stk .btn-ghost:hover {
  border-color: var(--nb-stk-sage);
  background: rgba(58,90,64,0.04);
}

.nb-stk .btn-solid {
  font-family: 'Mulish', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nb-stk-sand);
  background: var(--nb-stk-sage);
  border: 1.5px solid var(--nb-stk-sage);
  border-radius: 100px;
  padding: 8px 20px;
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
}

.nb-stk .btn-solid:hover {
  background: var(--nb-stk-sage-light);
  transform: translateY(-1px);
}

.nb-stk .hero {
  min-height: 680px;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  padding-top: 1rem;
}

.nb-stk .hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 70% 40%, rgba(58,90,64,0.06) 0%, transparent 60%), radial-gradient(ellipse at 20% 80%, rgba(139,111,71,0.06) 0%, transparent 50%);
}

.nb-stk .hero-circle {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.nb-stk .hc-1 {
  width: 500px;
  height: 500px;
  border: 1px solid rgba(58,90,64,0.1);
  top: 10%;
  right: -100px;
}

.nb-stk .hc-2 {
  width: 300px;
  height: 300px;
  border: 1px solid rgba(58,90,64,0.08);
  top: 25%;
  right: 50px;
}

.nb-stk .hc-3 {
  width: 40px;
  height: 40px;
  background: rgba(58,90,64,0.12);
  bottom: 30%;
  left: 10%;
  animation: nb-stk-float 8s ease-in-out infinite;
}

.nb-stk .hc-4 {
  width: 16px;
  height: 16px;
  background: var(--nb-stk-clay);
  opacity: 0.3;
  top: 35%;
  left: 25%;
  animation: nb-stk-float 12s ease-in-out infinite reverse;
  border-radius: 50%;
}

@keyframes nb-stk-float {
  0%,
    100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

.nb-stk .hero-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 4rem 3rem;
  position: relative;
  z-index: 1;
  width: 100%;
}

.nb-stk .hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--nb-stk-clay);
  margin-bottom: 2rem;
}

.nb-stk .hero-eyebrow::before {
  content: '';
  width: 28px;
  height: 1.5px;
  background: var(--nb-stk-clay);
}

.nb-stk h1 {
  font-family: 'Fraunces', serif;
  font-size: clamp(4rem, 7vw, 8rem);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--nb-stk-text);
  margin-bottom: 2rem;
  max-width: 14ch;
}

.nb-stk h1 em {
  font-style: italic;
  color: var(--nb-stk-sage);
}

.nb-stk .hero-body {
  font-size: 1.05rem;
  color: var(--nb-stk-muted);
  line-height: 1.7;
  max-width: 42ch;
  margin-bottom: 2.5rem;
}

.nb-stk .hero-ctas {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.nb-stk .cta-primary {
  font-family: 'Mulish', sans-serif;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--nb-stk-sand);
  background: var(--nb-stk-sage);
  border: none;
  border-radius: 100px;
  padding: 14px 32px;
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
  box-shadow: 0 4px 24px rgba(58,90,64,0.3);
}

.nb-stk .cta-primary:hover {
  background: var(--nb-stk-sage-light);
  transform: translateY(-2px);
}

.nb-stk .cta-secondary {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--nb-stk-muted);
  text-decoration: none;
  transition: color 0.2s, gap 0.2s;
}

.nb-stk .cta-secondary:hover {
  color: var(--nb-stk-sage);
  gap: 12px;
}

.nb-stk .section {
  padding: 8rem 0;
}

.nb-stk .section-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 3rem;
}

.nb-stk .section-label {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--nb-stk-clay);
  margin-bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 10px;
}

.nb-stk .section-label::after {
  content: '';
  flex: 1;
  max-width: 40px;
  height: 1px;
  background: var(--nb-stk-clay);
}

.nb-stk .section-title {
  font-family: 'Fraunces', serif;
  font-size: clamp(2.5rem, 4vw, 4rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  color: var(--nb-stk-text);
  line-height: 1.1;
  margin-bottom: 1.5rem;
}

.nb-stk .section-title em {
  font-style: italic;
  color: var(--nb-stk-sage);
}

.nb-stk .section-body {
  font-size: 1rem;
  color: var(--nb-stk-muted);
  max-width: 52ch;
  line-height: 1.75;
}

.nb-stk .feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5px;
  margin-top: 4rem;
  background: var(--nb-stk-warm-2);
  border: 1.5px solid var(--nb-stk-warm-2);
}

.nb-stk .feature-card {
  background: var(--nb-stk-sand);
  padding: 2.5rem 2rem;
  transition: background 0.2s;
}

.nb-stk .feature-card:hover {
  background: var(--nb-stk-warm);
}

.nb-stk .feature-num {
  font-family: 'Fraunces', serif;
  font-size: 2.5rem;
  font-weight: 900;
  color: rgba(58,90,64,0.12);
  line-height: 1;
  margin-bottom: 1rem;
}

.nb-stk .feature-title {
  font-family: 'Fraunces', serif;
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--nb-stk-text);
  margin-bottom: 0.75rem;
}

.nb-stk .feature-text {
  font-size: 0.85rem;
  color: var(--nb-stk-muted);
  line-height: 1.65;
}

.nb-stk .divider-section {
  background: var(--nb-stk-sage);
  padding: 5rem 0;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.nb-stk .divider-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(45deg, transparent, transparent 40px, rgba(255,255,255,0.02) 40px, rgba(255,255,255,0.02) 41px);
}

.nb-stk .divider-inner {
  position: relative;
  z-index: 1;
}

.nb-stk .divider-inner h2 {
  font-family: 'Fraunces', serif;
  font-size: clamp(2rem, 4vw, 4rem);
  font-weight: 300;
  font-style: italic;
  color: rgba(247,244,239,0.95);
  line-height: 1.2;
  margin-bottom: 2rem;
}

.nb-stk .divider-inner button {
  font-family: 'Mulish', sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nb-stk-sage);
  background: var(--nb-stk-sand);
  border: none;
  border-radius: 100px;
  padding: 13px 30px;
  cursor: pointer;
}

.nb-stk .long-section {
  padding: 6rem 0;
  border-top: 1px solid var(--nb-stk-warm-2);
}

.nb-stk .scroll-hint {
  position: absolute;
  bottom: 3rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--nb-stk-muted);
  z-index: 1;
}

.nb-stk .scroll-hint-text {
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.nb-stk .scroll-mouse {
  width: 22px;
  height: 34px;
  border: 1.5px solid var(--nb-stk-warm-2);
  border-radius: 12px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 6px;
}

.nb-stk .scroll-mouse::after {
  content: '';
  width: 4px;
  height: 8px;
  background: var(--nb-stk-sage);
  border-radius: 2px;
  animation: nb-stk-scrollDot 1.8s ease-in-out infinite;
}

@keyframes nb-stk-scrollDot {
  0%,
    100% {
    opacity: 1;
    transform: translateY(0);
  }

  50% {
    opacity: 0.2;
    transform: translateY(6px);
  }
}

@media (max-width: 768px) {
  .nb-stk .feature-grid {
    grid-template-columns: 1fr;
  }

  .nb-stk .hero-inner {
    padding: 3rem 1.5rem;
  }

  .nb-stk .section-inner {
    padding: 0 1.5rem;
  }

  .nb-stk .nav-links {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nb-stk nav,
    .nb-stk .brand-main,
    .nb-stk .brand-sub,
    .nb-stk .hc-3,
    .nb-stk .hc-4,
    .nb-stk .scroll-mouse::after {
    animation: none !important;
    transition: none !important;
  }
}
```

## JavaScript
```js
(() => {
  // Scoped sticky-scroll navbar wiring. The source mock bound a
  // `window.scroll` listener and read documentElement.scrollTop —
  // that would track the host gallery's scroll. Re-scope: use a
  // sentinel `hero` element to toggle the .scrolled class via
  // IntersectionObserver (fires when the hero top passes 60px below
  // the viewport top), and compute reading progress from the wrapper's
  // own bounding rect against the viewport.
  const root = document.querySelector('.nb-stk');
  if (!root) return;
  const nav = root.querySelector('[data-nb-stk-nav]');
  const progress = root.querySelector('[data-nb-stk-progress]');
  const hero = root.querySelector('.hero');
  if (!nav || !progress) return;

  // Toggle .scrolled when the hero crosses 60px below the viewport top.
  if (hero && 'IntersectionObserver' in window) {
    const io = new IntersectionObserver(
      ([entry]) => {
        // entry.boundingClientRect.top < -60 means the hero has scrolled
        // up past the threshold. We rely on entry.isIntersecting plus
        // the sentinel offset.
        const top = entry.boundingClientRect.top;
        nav.classList.toggle('scrolled', top < -60);
      },
      { threshold: [0], rootMargin: '-60px 0px 0px 0px' }
    );
    io.observe(hero);
  }

  function updateProgress() {
    const r = root.getBoundingClientRect();
    const vh = window.innerHeight;
    const total = root.offsetHeight - vh;
    if (total <= 0) { progress.style.transform = 'scaleX(0)'; return; }
    const scrolled = Math.min(Math.max(-r.top, 0), total);
    const pct = scrolled / total;
    progress.style.transform = 'scaleX(' + pct.toFixed(4) + ')';
  }
  window.addEventListener('scroll', updateProgress, { passive: true });
  window.addEventListener('resize', updateProgress);
  updateProgress();

  // Smooth scroll for in-page anchors.
  root.querySelectorAll('.nav-links a').forEach((a) => {
    a.addEventListener('click', (e) => {
      const href = a.getAttribute('href');
      if (!href || !href.startsWith('#')) return;
      const target = root.querySelector(href);
      if (!target) return;
      e.preventDefault();
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      root.querySelectorAll('.nav-links a').forEach((l) => l.classList.remove('active'));
      a.classList.add('active');
    });
  });
})();
```

Techniques used in this demo

Search CodeFronts

Loading…