25 CSS Sticky Navigations
15 / 25

All 25 in 25 CSS Sticky Navigations

  1. 01Pure CSS Sticky Navbar (No JavaScript, position:sticky)
  2. 02Sticky Top Header with Flexbox Layout (Logo · Links · CTA)
  3. 03Responsive Sticky Navbar with Hamburger Menu Toggle
  4. 04Sticky Header with Full-Bleed Bar and Container Max-Width Content
  5. 05Floating Glass Pill Sticky Navbar (Rounded, Backdrop Blur, Elevation)
  6. 06Sticky Header Shrink on Scroll (80px → 50px, Compact State)
  7. 07Sticky Nav with Directional Hide (Scroll Down) and Reveal (Scroll Up)
  8. 08Transparent-to-Solid Sticky Header on Hero Scroll
  9. 09Sticky Header with Article Reading Progress Bar Underneath
  10. 10Sticky Header with SVG Logo Wordmark → Mark-Only Morph on Scroll
  11. 11Sticky Navbar with Underline-Slide Active Link Indicator
  12. 12Sticky Header with Theme-Aware Token Swap (Light/Dark)
  13. 13Minimalist Sticky Header with Single High-Emphasis CTA
  14. 14Split Sticky Navbar with Centered Brand Logo (Links Left + Right)
  15. 15Neumorphic Sticky Navigation Bar (Soft-UI Inset/Outset Shadows)
  16. 16Sticky Header with Multi-Column Mega Menu Dropdown
  17. 17E-Commerce Sticky Header with Search Bar and Cart Badge Counter
  18. 18Sticky Announcement Bar Stacked Above Sticky Nav (Dual-Stick)
  19. 19Sticky Sub-Navigation Bar Locked Under the Main Sticky Header
  20. 20Sticky Header with Icon-to-Full-Width Search Expand on Click
  21. 21Sticky Sidebar Table of Contents That Follows a Long Article
  22. 22Mobile Bottom Tab Bar with Safe-Area Inset Padding
  23. 23Active-Section Scrollspy Highlighting in a Sticky Navbar
  24. 24Sticky Horizontal Scroll Tab Bar with Snap Points
  25. 25Sticky Header with Social Share Icons for Articles
Pure CSSMIT licensed

Neumorphic Sticky Navigation Bar (Soft-UI Inset/Outset Shadows)

Soft-UI applied to a sticky bar, with the physics done properly: every raised surface casts one light and one dark shadow from a single consistent light source, and active items invert to inset so they read as genuinely pressed into the panel. Includes the guardrails that keep the style usable — because the classic failure of this aesthetic is unreadable, un-focusable controls.

Live Demo
Plain CSSTailwind v4
Try it

Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49

Cruip · Affiliate link

Browse Cruip templates

The code

<section class="sn-15" aria-label="Neumorphic sticky navigation bar demo">
    <header class="sn-15__bar">
      <a class="sn-15__brand" href="#sn-15-s1"><span class="sn-15__logo" aria-hidden="true"></span>Pebble</a>
      <nav class="sn-15__nav" aria-label="Primary">
        <a class="sn-15__link" href="#sn-15-s1" aria-current="page">Home</a>
        <a class="sn-15__link" href="#sn-15-s2">Library</a>
        <a class="sn-15__link" href="#sn-15-s3">Devices</a>
      </nav>
      <button class="sn-15__icon" type="button" aria-label="Notifications, 3 unread">
        <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5zM10 20.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
        <span class="sn-15__badge" aria-hidden="true">3</span>
      </button>
    </header>
  <div class="sn-15__intro">
    <div class="sn-15__introin">
    <p class="sn-15__intro-eyebrow">one light source · paired shadows</p>
    <h2 class="sn-15__intro-title">Soft UI with guardrails</h2>
    </div>
  </div>
    <main>
      <section class="sn-15__sec" id="sn-15-s1">
        <h3 class="sn-15__h">Two shadows, one source</h3>
        <p class="sn-15__p">A light highlight at the top-left and a matching dark shadow at the bottom-right. Equal offsets, opposite colours — anything else looks tilted rather than raised.</p>
        <div class="sn-15__row">
          <span class="sn-15__tile">raised</span>
          <span class="sn-15__tile sn-15__tile--in">inset</span>
          <span class="sn-15__tile sn-15__tile--flat">flat</span>
        </div>
      </section>
      <section class="sn-15__sec" id="sn-15-s2">
        <h3 class="sn-15__h">Pressed means inset</h3>
        <p class="sn-15__p">The active nav item inverts both shadows so it sits inside the panel — and changes colour and weight too, because depth alone is invisible to plenty of users.</p>
      </section>
      <section class="sn-15__sec" id="sn-15-s3">
        <h3 class="sn-15__h">Focus needs a real outline</h3>
        <p class="sn-15__p">Soft shadows vanish in forced-colours mode. Every control here draws a genuine outline on <code>:focus-visible</code>, and <code>prefers-contrast: more</code> swaps the shadows for solid borders.</p>
      </section>
    </main>
  <footer class="sn-15__pagefoot">
    <p class="sn-15__pagefootin">Sticky navigation pattern 15 of 25 · codefronts.com</p>
  </footer>
</section>
.sn-15 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--sn-15-bg);
  --sn-15-bg: oklch(0.925 0.008 265);
  --sn-15-lit: oklch(1 0 0);
  --sn-15-dim: oklch(0.78 0.016 265);
  --sn-15-ink: oklch(0.3 0.024 265);
  --sn-15-mut: oklch(0.48 0.02 265);
  --sn-15-acc: oklch(0.52 0.16 285);
  --sn-15-h: 74px;
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--sn-15-ink);
  -webkit-font-smoothing: antialiased;
}

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

.sn-15__eyebrow,
.sn-15__intro-eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--sn-15-acc);
}

.sn-15__title,
.sn-15__intro-title {
  font-size: clamp(23px,3.5vw,36px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.sn-15__bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: clamp(10px,2vw,20px);
  min-height: var(--sn-15-h);
  padding: 0 clamp(14px,2.6vw,22px);
  background: var(--sn-15-bg);
  box-shadow: 0 8px 18px -12px var(--sn-15-dim);
}

.sn-15__brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-size: 15.5px;
  font-weight: 700;
  letter-spacing: -.02em;
  color: inherit;
  text-decoration: none;
}

.sn-15__logo {
  width: 34px;
  height: 34px;
  border-radius: 12px;
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 8px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
}

.sn-15__nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-inline-start: auto;
}

.sn-15__link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 14px;
  font-size: 13.6px;
  font-weight: 600;
  color: var(--sn-15-mut);
  text-decoration: none;
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
  transition: box-shadow .22s ease,color .18s ease;
}

.sn-15__link:hover {
  color: var(--sn-15-ink);
  box-shadow: -2px -2px 6px var(--sn-15-lit),2px 2px 7px var(--sn-15-dim);
}

.sn-15__link[aria-current="page"] {
  color: var(--sn-15-acc);
  font-weight: 700;
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__icon {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 0;
  border-radius: 14px;
  background: var(--sn-15-bg);
  color: var(--sn-15-mut);
  cursor: pointer;
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
  transition: box-shadow .22s ease,color .18s ease;
}

.sn-15__icon:hover {
  color: var(--sn-15-ink);
}

.sn-15__icon:active {
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__icon svg {
  inline-size: 19px;
  block-size: 19px;
}

.sn-15__badge {
  position: absolute;
  inset-block-start: 6px;
  inset-inline-end: 6px;
  display: grid;
  place-items: center;
  min-inline-size: 16px;
  block-size: 16px;
  padding-inline: 4px;
  border-radius: 99px;
  background: var(--sn-15-acc);
  color: oklch(1 0 0);
  font-size: 10px;
  font-weight: 700;
}

.sn-15__link:focus-visible,
.sn-15__icon:focus-visible,
.sn-15__brand:focus-visible {
  outline: 2.5px solid var(--sn-15-acc);
  outline-offset: 3px;
}

.sn-15__sec {
  scroll-margin-top: var(--sn-15-h);
  display: grid;
  gap: 12px;
  align-content: center;
  min-height: 66svh;
  padding: clamp(26px,4.4vw,50px);
}

.sn-15__h {
  font-size: clamp(19px,2.6vw,25px);
  line-height: 1.18;
  letter-spacing: -.025em;
  font-weight: 700;
  text-wrap: balance;
}

.sn-15__p {
  max-width: 52ch;
  font-size: 14.8px;
  line-height: 1.68;
  color: var(--sn-15-mut);
  text-wrap: pretty;
}

.sn-15__p code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .85em;
  padding: 2px 7px;
  border-radius: 6px;
  background: var(--sn-15-bg);
  box-shadow: inset -2px -2px 4px var(--sn-15-lit),inset 2px 2px 4px var(--sn-15-dim);
}

.sn-15__row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-block-start: 4px;
}

.sn-15__tile {
  display: grid;
  place-items: center;
  min-inline-size: 92px;
  min-block-size: 44px;
  padding-inline: 14px;
  border-radius: 14px;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--sn-15-mut);
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
}

.sn-15__tile--in {
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__tile--flat {
  box-shadow: none;
  border: 1px solid oklch(0.3 0.024 265/.14);
}

@media (prefers-contrast: more) {
  .sn-15__link,
    .sn-15__icon,
    .sn-15__logo,
    .sn-15__tile {
    box-shadow: none;
    border: 2px solid var(--sn-15-ink);
  }

  .sn-15__link[aria-current="page"] {
    background: var(--sn-15-acc);
    color: oklch(1 0 0);
  }
}

@media (max-width: 560px) {
  .sn-15__nav {
    gap: 5px;
  }

  .sn-15__link {
    padding: 0 12px;
    font-size: 12.8px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sn-15 * {
    transition-duration: .01ms !important;
  }
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */

:root:has(.sn-15) {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--sn-15-h) + 10px);
}

@media (prefers-reduced-motion: reduce) {
  :root:has(.sn-15) {
    scroll-behavior: auto;
  }
}

.sn-15__bar,
.sn-15__barin {
  padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}

.sn-15__sec {
  padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}

.sn-15__intro {
  display: grid;
  place-items: center;
  min-height: min(90svh,900px);
  padding: clamp(56px,12vh,150px) clamp(20px,5vw,48px) clamp(40px,8vh,90px);
}

.sn-15__introin {
  display: grid;
  justify-items: center;
  gap: clamp(12px,1.8vw,18px);
  text-align: center;
  max-width: 26ch;
}

.sn-15__intro-eyebrow {
  font-size: clamp(11px,1.1vw,13px);
}

.sn-15__intro-title {
  font-size: clamp(38px,7.5vw,86px);
  line-height: .98;
  letter-spacing: -.04em;
  max-width: none;
}

.sn-15__intro-sub {
  font-size: clamp(15px,1.7vw,19px);
  line-height: 1.55;
  max-width: 46ch;
}

.sn-15__intro::after {
  content: "Scroll — the bar stays";
  justify-self: center;
  margin-block-start: clamp(20px,4vh,44px);
  padding: 9px 18px;
  border: 1px solid var(--sn-15-line);
  border-radius: 99px;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sn-15-mut);
  animation: sn-15-cue 2.6s ease-in-out infinite;
}

@keyframes sn-15-cue {
  0%,
    100% {
    transform: translateY(0);
    opacity: .75;
  }

  50% {
    transform: translateY(5px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sn-15__intro::after {
    animation: none;
  }
}

.sn-15__pagefoot {
  display: grid;
  place-items: center;
  padding: clamp(44px,9vh,110px) 24px;
  border-top: 1px solid var(--sn-15-line);
}

.sn-15__pagefootin {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 12px;
  letter-spacing: .06em;
  color: var(--sn-15-mut);
  text-align: center;
}

.sn-15__intro-title,
.sn-15__intro-sub {
  text-wrap: balance;
}
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 Sticky Navigation 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: Neumorphic Sticky Navigation Bar (Soft-UI Inset/Outset Shadows)
Source: https://codefronts.com/navigation/css-sticky-navigation/neumorphic-sticky-navigation-bar-soft-ui-inset-outset-shadows/

Soft-UI applied to a sticky bar, with the physics done properly: every raised surface casts one light and one dark shadow from a single consistent light source, and active items invert to inset so they read as genuinely pressed into the panel. Includes the guardrails that keep the style usable — because the classic failure of this aesthetic is unreadable, un-focusable controls.
## HTML
```html
<section class="sn-15" aria-label="Neumorphic sticky navigation bar demo">
    <header class="sn-15__bar">
      <a class="sn-15__brand" href="#sn-15-s1"><span class="sn-15__logo" aria-hidden="true"></span>Pebble</a>
      <nav class="sn-15__nav" aria-label="Primary">
        <a class="sn-15__link" href="#sn-15-s1" aria-current="page">Home</a>
        <a class="sn-15__link" href="#sn-15-s2">Library</a>
        <a class="sn-15__link" href="#sn-15-s3">Devices</a>
      </nav>
      <button class="sn-15__icon" type="button" aria-label="Notifications, 3 unread">
        <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M18 15.5V11a6 6 0 1 0-12 0v4.5L4.5 18h15L18 15.5zM10 20.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
        <span class="sn-15__badge" aria-hidden="true">3</span>
      </button>
    </header>
  <div class="sn-15__intro">
    <div class="sn-15__introin">
    <p class="sn-15__intro-eyebrow">one light source · paired shadows</p>
    <h2 class="sn-15__intro-title">Soft UI with guardrails</h2>
    </div>
  </div>
    <main>
      <section class="sn-15__sec" id="sn-15-s1">
        <h3 class="sn-15__h">Two shadows, one source</h3>
        <p class="sn-15__p">A light highlight at the top-left and a matching dark shadow at the bottom-right. Equal offsets, opposite colours — anything else looks tilted rather than raised.</p>
        <div class="sn-15__row">
          <span class="sn-15__tile">raised</span>
          <span class="sn-15__tile sn-15__tile--in">inset</span>
          <span class="sn-15__tile sn-15__tile--flat">flat</span>
        </div>
      </section>
      <section class="sn-15__sec" id="sn-15-s2">
        <h3 class="sn-15__h">Pressed means inset</h3>
        <p class="sn-15__p">The active nav item inverts both shadows so it sits inside the panel — and changes colour and weight too, because depth alone is invisible to plenty of users.</p>
      </section>
      <section class="sn-15__sec" id="sn-15-s3">
        <h3 class="sn-15__h">Focus needs a real outline</h3>
        <p class="sn-15__p">Soft shadows vanish in forced-colours mode. Every control here draws a genuine outline on <code>:focus-visible</code>, and <code>prefers-contrast: more</code> swaps the shadows for solid borders.</p>
      </section>
    </main>
  <footer class="sn-15__pagefoot">
    <p class="sn-15__pagefootin">Sticky navigation pattern 15 of 25 · codefronts.com</p>
  </footer>
</section>
```
## CSS
```css
.sn-15 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--sn-15-bg);
  --sn-15-bg: oklch(0.925 0.008 265);
  --sn-15-lit: oklch(1 0 0);
  --sn-15-dim: oklch(0.78 0.016 265);
  --sn-15-ink: oklch(0.3 0.024 265);
  --sn-15-mut: oklch(0.48 0.02 265);
  --sn-15-acc: oklch(0.52 0.16 285);
  --sn-15-h: 74px;
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--sn-15-ink);
  -webkit-font-smoothing: antialiased;
}

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

.sn-15__eyebrow,
.sn-15__intro-eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--sn-15-acc);
}

.sn-15__title,
.sn-15__intro-title {
  font-size: clamp(23px,3.5vw,36px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.sn-15__bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: clamp(10px,2vw,20px);
  min-height: var(--sn-15-h);
  padding: 0 clamp(14px,2.6vw,22px);
  background: var(--sn-15-bg);
  box-shadow: 0 8px 18px -12px var(--sn-15-dim);
}

.sn-15__brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-size: 15.5px;
  font-weight: 700;
  letter-spacing: -.02em;
  color: inherit;
  text-decoration: none;
}

.sn-15__logo {
  width: 34px;
  height: 34px;
  border-radius: 12px;
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 8px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
}

.sn-15__nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-inline-start: auto;
}

.sn-15__link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 14px;
  font-size: 13.6px;
  font-weight: 600;
  color: var(--sn-15-mut);
  text-decoration: none;
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
  transition: box-shadow .22s ease,color .18s ease;
}

.sn-15__link:hover {
  color: var(--sn-15-ink);
  box-shadow: -2px -2px 6px var(--sn-15-lit),2px 2px 7px var(--sn-15-dim);
}

.sn-15__link[aria-current="page"] {
  color: var(--sn-15-acc);
  font-weight: 700;
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__icon {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 0;
  border-radius: 14px;
  background: var(--sn-15-bg);
  color: var(--sn-15-mut);
  cursor: pointer;
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
  transition: box-shadow .22s ease,color .18s ease;
}

.sn-15__icon:hover {
  color: var(--sn-15-ink);
}

.sn-15__icon:active {
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__icon svg {
  inline-size: 19px;
  block-size: 19px;
}

.sn-15__badge {
  position: absolute;
  inset-block-start: 6px;
  inset-inline-end: 6px;
  display: grid;
  place-items: center;
  min-inline-size: 16px;
  block-size: 16px;
  padding-inline: 4px;
  border-radius: 99px;
  background: var(--sn-15-acc);
  color: oklch(1 0 0);
  font-size: 10px;
  font-weight: 700;
}

.sn-15__link:focus-visible,
.sn-15__icon:focus-visible,
.sn-15__brand:focus-visible {
  outline: 2.5px solid var(--sn-15-acc);
  outline-offset: 3px;
}

.sn-15__sec {
  scroll-margin-top: var(--sn-15-h);
  display: grid;
  gap: 12px;
  align-content: center;
  min-height: 66svh;
  padding: clamp(26px,4.4vw,50px);
}

.sn-15__h {
  font-size: clamp(19px,2.6vw,25px);
  line-height: 1.18;
  letter-spacing: -.025em;
  font-weight: 700;
  text-wrap: balance;
}

.sn-15__p {
  max-width: 52ch;
  font-size: 14.8px;
  line-height: 1.68;
  color: var(--sn-15-mut);
  text-wrap: pretty;
}

.sn-15__p code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .85em;
  padding: 2px 7px;
  border-radius: 6px;
  background: var(--sn-15-bg);
  box-shadow: inset -2px -2px 4px var(--sn-15-lit),inset 2px 2px 4px var(--sn-15-dim);
}

.sn-15__row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-block-start: 4px;
}

.sn-15__tile {
  display: grid;
  place-items: center;
  min-inline-size: 92px;
  min-block-size: 44px;
  padding-inline: 14px;
  border-radius: 14px;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--sn-15-mut);
  background: var(--sn-15-bg);
  box-shadow: -4px -4px 9px var(--sn-15-lit),4px 4px 10px var(--sn-15-dim);
}

.sn-15__tile--in {
  box-shadow: inset -3px -3px 7px var(--sn-15-lit),inset 3px 3px 7px var(--sn-15-dim);
}

.sn-15__tile--flat {
  box-shadow: none;
  border: 1px solid oklch(0.3 0.024 265/.14);
}

@media (prefers-contrast: more) {
  .sn-15__link,
    .sn-15__icon,
    .sn-15__logo,
    .sn-15__tile {
    box-shadow: none;
    border: 2px solid var(--sn-15-ink);
  }

  .sn-15__link[aria-current="page"] {
    background: var(--sn-15-acc);
    color: oklch(1 0 0);
  }
}

@media (max-width: 560px) {
  .sn-15__nav {
    gap: 5px;
  }

  .sn-15__link {
    padding: 0 12px;
    font-size: 12.8px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sn-15 * {
    transition-duration: .01ms !important;
  }
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */

:root:has(.sn-15) {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--sn-15-h) + 10px);
}

@media (prefers-reduced-motion: reduce) {
  :root:has(.sn-15) {
    scroll-behavior: auto;
  }
}

.sn-15__bar,
.sn-15__barin {
  padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}

.sn-15__sec {
  padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}

.sn-15__intro {
  display: grid;
  place-items: center;
  min-height: min(90svh,900px);
  padding: clamp(56px,12vh,150px) clamp(20px,5vw,48px) clamp(40px,8vh,90px);
}

.sn-15__introin {
  display: grid;
  justify-items: center;
  gap: clamp(12px,1.8vw,18px);
  text-align: center;
  max-width: 26ch;
}

.sn-15__intro-eyebrow {
  font-size: clamp(11px,1.1vw,13px);
}

.sn-15__intro-title {
  font-size: clamp(38px,7.5vw,86px);
  line-height: .98;
  letter-spacing: -.04em;
  max-width: none;
}

.sn-15__intro-sub {
  font-size: clamp(15px,1.7vw,19px);
  line-height: 1.55;
  max-width: 46ch;
}

.sn-15__intro::after {
  content: "Scroll — the bar stays";
  justify-self: center;
  margin-block-start: clamp(20px,4vh,44px);
  padding: 9px 18px;
  border: 1px solid var(--sn-15-line);
  border-radius: 99px;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sn-15-mut);
  animation: sn-15-cue 2.6s ease-in-out infinite;
}

@keyframes sn-15-cue {
  0%,
    100% {
    transform: translateY(0);
    opacity: .75;
  }

  50% {
    transform: translateY(5px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sn-15__intro::after {
    animation: none;
  }
}

.sn-15__pagefoot {
  display: grid;
  place-items: center;
  padding: clamp(44px,9vh,110px) 24px;
  border-top: 1px solid var(--sn-15-line);
}

.sn-15__pagefootin {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 12px;
  letter-spacing: .06em;
  color: var(--sn-15-mut);
  text-align: center;
}

.sn-15__intro-title,
.sn-15__intro-sub {
  text-wrap: balance;
}
```

How this works

Neumorphism is one idea repeated: a surface the same colour as its background, lifted by two opposing shadows.

--bg: oklch(0.93 0.008 260);
--lit: oklch(1 0 0);                       /* highlight, top-left */
--dim: oklch(0.78 0.014 260);              /* shadow, bottom-right */

.raised{ background: var(--bg);
  box-shadow: -5px -5px 10px var(--lit), 5px 5px 12px var(--dim) }

.pressed{ box-shadow:
  inset -3px -3px 7px var(--lit), inset 3px 3px 7px var(--dim) }

The rules that make it convincing are physical, not decorative. One light source for the entire interface — if the header lights from the top-left, every element does, forever. The two shadows must be equal in offset and opposite in colour, or the surface looks tilted. And the base colour cannot be pure white or pure black: both shadows need room to move, which is why soft-UI is always built on a mid-light grey.

Then the guardrails. This aesthetic is notorious for failing accessibility because it encodes state purely in shadow depth, which is invisible to anyone with low vision and gone entirely in high-contrast mode. So here the active item also changes text colour and weight, focus rings are drawn as a real outline rather than a softer shadow, and label text sits well above the contrast threshold against the panel. Under prefers-contrast: more the shadows are replaced with solid borders.

Make it yours

  • Depth: scale both offsets and both blur radii together. Halve them for a subtle surface, double them for a pronounced one — never change one side alone.
  • Light direction: flip both offsets' signs to move the source to the bottom-right; keep every element in the UI consistent.
  • Tinted soft-UI: derive both shadow colours from the base with color-mix() so a hue change re-derives the whole palette.
  • Claymorphism variant: add a large soft outer shadow plus an inner top highlight and increase the radius — same shadow pair, rounder and puffier.

Gotchas — read before shipping

  • Neutral-grey shadows on a tinted background look dirty. Derive both shadow colours from the surface hue.
  • Inconsistent light direction is the single most common giveaway of a hand-rolled soft-UI: one element lit from the top-left next to another lit from below.
  • State encoded only as depth is invisible to low-vision users and disappears in forced-colours mode. Always pair depth with a colour or weight change.
  • Soft shadows cannot serve as focus indicators — they do not survive high-contrast modes. Use a real outline.
  • Many soft-shadow layers on a sticky element repaint on every scroll frame. Keep the stack to two shadows per surface.
  • Pure white or pure black backgrounds leave no room for a highlight or a shadow, so the effect collapses.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by :has(), oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.

Multiple and inset box-shadows are universally supported. prefers-contrast is available in Chrome 96, Safari 14.1 and Firefox 101; without it the shadow styling simply remains.

Techniques used in this demo

Search CodeFronts

Loading…