25 CSS Sticky Navigation16 / 25
Sticky Header with Multi-Column Mega Menu Dropdown
Category navigation for a catalogue that does not fit in a list: a full-width panel with grouped columns and a promoted product, opening from a sticky header so it stays reachable at any scroll depth. Built as a disclosure rather than a menu, which is what lets it work identically for pointer, keyboard and touch.
Published
The code
<section class="sn-16" aria-label="Multi-column mega menu demo">
<header class="sn-16__bar" id="sn-16-bar">
<a class="sn-16__brand" href="#sn-16-s1"><span class="sn-16__logo" aria-hidden="true"></span>Forge&Field</a>
<nav class="sn-16__nav" aria-label="Primary">
<button class="sn-16__trigger" type="button" id="sn-16-trigger" aria-expanded="false" aria-controls="sn-16-mega">
Shop
<svg class="sn-16__caret" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M2.5 4.5 6 8l3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<a class="sn-16__link" href="#sn-16-s2">Workshop</a>
<a class="sn-16__link" href="#sn-16-s3">Stories</a>
</nav>
<a class="sn-16__cta" href="#sn-16-s3">Bag</a>
<div class="sn-16__mega" id="sn-16-mega" hidden>
<div class="sn-16__col">
<h3 class="sn-16__ch">Hand tools</h3>
<a class="sn-16__mlink" href="#sn-16-s1">Chisels & gouges</a>
<a class="sn-16__mlink" href="#sn-16-s1">Planes</a>
<a class="sn-16__mlink" href="#sn-16-s1">Marking & measuring</a>
<a class="sn-16__mlink" href="#sn-16-s1">Sharpening</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Workshop</h3>
<a class="sn-16__mlink" href="#sn-16-s2">Benches</a>
<a class="sn-16__mlink" href="#sn-16-s2">Vices & clamps</a>
<a class="sn-16__mlink" href="#sn-16-s2">Dust extraction</a>
<a class="sn-16__mlink" href="#sn-16-s2">Storage</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Materials</h3>
<a class="sn-16__mlink" href="#sn-16-s3">Hardwoods</a>
<a class="sn-16__mlink" href="#sn-16-s3">Finishes & oils</a>
<a class="sn-16__mlink" href="#sn-16-s3">Adhesives</a>
<a class="sn-16__mlink" href="#sn-16-s3">Abrasives</a>
</div>
<a class="sn-16__promo" href="#sn-16-s1">
<img class="sn-16__promoimg" src="https://images.unsplash.com/photo-1504148455328-c376907d081c?q=80&w=800&auto=format&fit=crop" alt="Woodworking hand tools arranged on a workbench" width="800" height="600" loading="lazy" decoding="async">
<span class="sn-16__promotag">New season</span>
<span class="sn-16__promoh">The bench collection</span>
</a>
</div>
</header>
<main>
<section class="sn-16__sec" id="sn-16-s1">
<p class="sn-16__kicker">Disclosure, not menu</p>
<h3 class="sn-16__h">Links should behave like links</h3>
<p class="sn-16__p">An ARIA menu changes the keyboard contract to arrow-keys-only and announces items as application commands. A disclosure keeps Tab, keeps link semantics and works on touch.</p>
</section>
<section class="sn-16__sec" id="sn-16-s2">
<p class="sn-16__kicker">Grace period</p>
<h3 class="sn-16__h">Diagonal mouse paths</h3>
<p class="sn-16__p">Closing the instant the pointer leaves the trigger breaks the natural diagonal travel toward the panel. A short delay makes it feel forgiving without feeling stuck.</p>
</section>
<section class="sn-16__sec" id="sn-16-s3">
<p class="sn-16__kicker">Sticky context</p>
<h3 class="sn-16__h">The panel travels with the bar</h3>
<p class="sn-16__p">Absolutely positioned inside the sticky header, the panel needs no coordinate maths — it is pinned wherever the header is.</p>
</section>
</main>
<footer class="sn-16__pagefoot">
<p class="sn-16__pagefootin">Sticky navigation pattern 16 of 25 · codefronts.com</p>
</footer>
</section><section class="sn-16" aria-label="Multi-column mega menu demo">
<header class="sn-16__bar" id="sn-16-bar">
<a class="sn-16__brand" href="#sn-16-s1"><span class="sn-16__logo" aria-hidden="true"></span>Forge&Field</a>
<nav class="sn-16__nav" aria-label="Primary">
<button class="sn-16__trigger" type="button" id="sn-16-trigger" aria-expanded="false" aria-controls="sn-16-mega">
Shop
<svg class="sn-16__caret" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M2.5 4.5 6 8l3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<a class="sn-16__link" href="#sn-16-s2">Workshop</a>
<a class="sn-16__link" href="#sn-16-s3">Stories</a>
</nav>
<a class="sn-16__cta" href="#sn-16-s3">Bag</a>
<div class="sn-16__mega" id="sn-16-mega" hidden>
<div class="sn-16__col">
<h3 class="sn-16__ch">Hand tools</h3>
<a class="sn-16__mlink" href="#sn-16-s1">Chisels & gouges</a>
<a class="sn-16__mlink" href="#sn-16-s1">Planes</a>
<a class="sn-16__mlink" href="#sn-16-s1">Marking & measuring</a>
<a class="sn-16__mlink" href="#sn-16-s1">Sharpening</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Workshop</h3>
<a class="sn-16__mlink" href="#sn-16-s2">Benches</a>
<a class="sn-16__mlink" href="#sn-16-s2">Vices & clamps</a>
<a class="sn-16__mlink" href="#sn-16-s2">Dust extraction</a>
<a class="sn-16__mlink" href="#sn-16-s2">Storage</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Materials</h3>
<a class="sn-16__mlink" href="#sn-16-s3">Hardwoods</a>
<a class="sn-16__mlink" href="#sn-16-s3">Finishes & oils</a>
<a class="sn-16__mlink" href="#sn-16-s3">Adhesives</a>
<a class="sn-16__mlink" href="#sn-16-s3">Abrasives</a>
</div>
<a class="sn-16__promo" href="#sn-16-s1">
<img class="sn-16__promoimg" src="https://images.unsplash.com/photo-1504148455328-c376907d081c?q=80&w=800&auto=format&fit=crop" alt="Woodworking hand tools arranged on a workbench" width="800" height="600" loading="lazy" decoding="async">
<span class="sn-16__promotag">New season</span>
<span class="sn-16__promoh">The bench collection</span>
</a>
</div>
</header>
<main>
<section class="sn-16__sec" id="sn-16-s1">
<p class="sn-16__kicker">Disclosure, not menu</p>
<h3 class="sn-16__h">Links should behave like links</h3>
<p class="sn-16__p">An ARIA menu changes the keyboard contract to arrow-keys-only and announces items as application commands. A disclosure keeps Tab, keeps link semantics and works on touch.</p>
</section>
<section class="sn-16__sec" id="sn-16-s2">
<p class="sn-16__kicker">Grace period</p>
<h3 class="sn-16__h">Diagonal mouse paths</h3>
<p class="sn-16__p">Closing the instant the pointer leaves the trigger breaks the natural diagonal travel toward the panel. A short delay makes it feel forgiving without feeling stuck.</p>
</section>
<section class="sn-16__sec" id="sn-16-s3">
<p class="sn-16__kicker">Sticky context</p>
<h3 class="sn-16__h">The panel travels with the bar</h3>
<p class="sn-16__p">Absolutely positioned inside the sticky header, the panel needs no coordinate maths — it is pinned wherever the header is.</p>
</section>
</main>
<footer class="sn-16__pagefoot">
<p class="sn-16__pagefootin">Sticky navigation pattern 16 of 25 · codefronts.com</p>
</footer>
</section>.sn-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--sn-16-bg);
--sn-16-bg: oklch(0.95 0.012 75);
--sn-16-surface: oklch(0.995 0.004 75);
--sn-16-ink: oklch(0.23 0.024 55);
--sn-16-mut: oklch(0.53 0.022 55);
--sn-16-acc: oklch(0.5 0.13 42);
--sn-16-line: oklch(0.23 0.024 55/.13);
--sn-16-h: 66px;
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--sn-16-ink);
-webkit-font-smoothing: antialiased;
}
.sn-16 *,
.sn-16 *::before,
.sn-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sn-16__bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: clamp(10px,2vw,24px);
min-height: var(--sn-16-h);
padding: 0 clamp(14px,2.6vw,24px);
background: color-mix(in oklch,var(--sn-16-surface) 90%,transparent);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 15px;
font-weight: 700;
letter-spacing: -.025em;
color: inherit;
text-decoration: none;
white-space: nowrap;
}
.sn-16__logo {
width: 20px;
height: 20px;
border-radius: 5px;
background: var(--sn-16-acc);
clip-path: polygon(0 20%,100% 0,80% 100%,20% 80%);
}
.sn-16__nav {
display: flex;
align-items: center;
gap: 2px;
margin-inline-start: auto;
}
.sn-16__trigger {
display: flex;
align-items: center;
gap: 6px;
min-height: 38px;
padding: 0 12px;
border: 0;
border-radius: 9px;
background: transparent;
font-family: inherit;
font-size: 13.6px;
font-weight: 640;
color: var(--sn-16-mut);
cursor: pointer;
transition: color .18s ease,background .18s ease;
}
.sn-16__trigger:hover,
.sn-16__trigger[aria-expanded="true"] {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.06);
}
.sn-16__caret {
inline-size: 12px;
block-size: 12px;
transition: rotate .26s ease;
}
.sn-16__trigger[aria-expanded="true"] .sn-16__caret {
rotate: 180deg;
}
.sn-16__link {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 12px;
border-radius: 9px;
font-size: 13.6px;
font-weight: 600;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .18s ease,background .18s ease;
}
.sn-16__link:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__cta {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 16px;
border-radius: 99px;
font-size: 13.2px;
font-weight: 660;
color: oklch(1 0 0);
background: var(--sn-16-acc);
text-decoration: none;
white-space: nowrap;
transition: transform .18s ease;
}
.sn-16__cta:hover {
transform: translateY(-1px);
}
.sn-16__trigger:focus-visible,
.sn-16__link:focus-visible,
.sn-16__cta:focus-visible,
.sn-16__brand:focus-visible,
.sn-16__mlink:focus-visible,
.sn-16__promo:focus-visible {
outline: 2px solid var(--sn-16-acc);
outline-offset: 2px;
}
.sn-16__mega {
position: absolute;
inset-inline: 0;
top: 100%;
display: grid;
grid-template-columns: repeat(3,minmax(0,1fr)) 250px;
gap: clamp(16px,2.6vw,32px);
padding: clamp(20px,3vw,30px);
background: var(--sn-16-surface);
border-bottom: 1px solid var(--sn-16-line);
box-shadow: 0 30px 44px -30px oklch(0.23 0.024 55/.5);
max-block-size: min(62svh,420px);
overflow: auto;
opacity: 1;
translate: 0 0;
transition: opacity .22s ease,translate .26s cubic-bezier(.32,.72,.3,1);
}
@starting-style {
.sn-16__mega {
opacity: 0;
translate: 0 -10px;
}
}
.sn-16__mega[hidden] {
display: none;
}
.sn-16__col {
display: grid;
align-content: start;
gap: 3px;
min-width: 0;
}
.sn-16__ch {
margin-block-end: 7px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10.5px;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__mlink {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 10px;
margin-inline: -10px;
border-radius: 8px;
font-size: 14px;
font-weight: 560;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .16s ease,background .16s ease;
}
.sn-16__mlink:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__promo {
position: relative;
display: grid;
align-content: end;
gap: 4px;
min-block-size: 160px;
padding: 16px;
border-radius: 12px;
overflow: hidden;
color: oklch(1 0 0);
text-decoration: none;
background: linear-gradient(160deg,oklch(0.5 0.1 45),oklch(0.35 0.06 60));
}
.sn-16__promoimg {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
opacity: .62;
transition: scale .5s cubic-bezier(.32,.72,.3,1);
}
.sn-16__promo:hover .sn-16__promoimg {
scale: 1.06;
}
.sn-16__promotag,
.sn-16__promoh {
position: relative;
}
.sn-16__promotag {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
opacity: .92;
}
.sn-16__promoh {
font-size: 17px;
font-weight: 680;
letter-spacing: -.02em;
text-shadow: 0 1px 12px oklch(0 0 0/.5);
}
.sn-16__sec {
scroll-margin-top: var(--sn-16-h);
display: grid;
gap: 10px;
align-content: center;
min-height: 66svh;
padding: clamp(24px,4vw,48px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__sec:nth-child(even) {
background: oklch(0.975 0.008 70);
}
.sn-16__kicker {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__h {
font-size: clamp(19px,2.6vw,26px);
line-height: 1.16;
letter-spacing: -.025em;
font-weight: 690;
text-wrap: balance;
}
.sn-16__p {
max-width: 54ch;
font-size: 14.8px;
line-height: 1.64;
color: var(--sn-16-mut);
text-wrap: pretty;
}
@media (max-width: 780px) {
.sn-16__mega {
grid-template-columns: repeat(2,minmax(0,1fr));
}
.sn-16__promo {
grid-column: 1/-1;
min-block-size: 120px;
}
}
@media (max-width: 520px) {
.sn-16__mega {
grid-template-columns: 1fr;
}
.sn-16__link {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.sn-16 * {
transition-duration: .01ms !important;
}
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */
:root:has(.sn-16) {
scroll-behavior: smooth;
scroll-padding-top: calc(var(--sn-16-h) + 8px);
}
@media (prefers-reduced-motion: reduce) {
:root:has(.sn-16) {
scroll-behavior: auto;
}
}
.sn-16__bar,
.sn-16__barin {
padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}
.sn-16__sec {
padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}
.sn-16__pagefoot {
display: grid;
place-items: center;
padding: clamp(44px,9vh,110px) 24px;
border-top: 1px solid var(--sn-16-line);
}
.sn-16__pagefootin {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
letter-spacing: .06em;
color: var(--sn-16-mut);
text-align: center;
}
.sn-16__intro-title,
.sn-16__intro-sub {
text-wrap: balance;
}.sn-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--sn-16-bg);
--sn-16-bg: oklch(0.95 0.012 75);
--sn-16-surface: oklch(0.995 0.004 75);
--sn-16-ink: oklch(0.23 0.024 55);
--sn-16-mut: oklch(0.53 0.022 55);
--sn-16-acc: oklch(0.5 0.13 42);
--sn-16-line: oklch(0.23 0.024 55/.13);
--sn-16-h: 66px;
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--sn-16-ink);
-webkit-font-smoothing: antialiased;
}
.sn-16 *,
.sn-16 *::before,
.sn-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sn-16__bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: clamp(10px,2vw,24px);
min-height: var(--sn-16-h);
padding: 0 clamp(14px,2.6vw,24px);
background: color-mix(in oklch,var(--sn-16-surface) 90%,transparent);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 15px;
font-weight: 700;
letter-spacing: -.025em;
color: inherit;
text-decoration: none;
white-space: nowrap;
}
.sn-16__logo {
width: 20px;
height: 20px;
border-radius: 5px;
background: var(--sn-16-acc);
clip-path: polygon(0 20%,100% 0,80% 100%,20% 80%);
}
.sn-16__nav {
display: flex;
align-items: center;
gap: 2px;
margin-inline-start: auto;
}
.sn-16__trigger {
display: flex;
align-items: center;
gap: 6px;
min-height: 38px;
padding: 0 12px;
border: 0;
border-radius: 9px;
background: transparent;
font-family: inherit;
font-size: 13.6px;
font-weight: 640;
color: var(--sn-16-mut);
cursor: pointer;
transition: color .18s ease,background .18s ease;
}
.sn-16__trigger:hover,
.sn-16__trigger[aria-expanded="true"] {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.06);
}
.sn-16__caret {
inline-size: 12px;
block-size: 12px;
transition: rotate .26s ease;
}
.sn-16__trigger[aria-expanded="true"] .sn-16__caret {
rotate: 180deg;
}
.sn-16__link {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 12px;
border-radius: 9px;
font-size: 13.6px;
font-weight: 600;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .18s ease,background .18s ease;
}
.sn-16__link:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__cta {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 16px;
border-radius: 99px;
font-size: 13.2px;
font-weight: 660;
color: oklch(1 0 0);
background: var(--sn-16-acc);
text-decoration: none;
white-space: nowrap;
transition: transform .18s ease;
}
.sn-16__cta:hover {
transform: translateY(-1px);
}
.sn-16__trigger:focus-visible,
.sn-16__link:focus-visible,
.sn-16__cta:focus-visible,
.sn-16__brand:focus-visible,
.sn-16__mlink:focus-visible,
.sn-16__promo:focus-visible {
outline: 2px solid var(--sn-16-acc);
outline-offset: 2px;
}
.sn-16__mega {
position: absolute;
inset-inline: 0;
top: 100%;
display: grid;
grid-template-columns: repeat(3,minmax(0,1fr)) 250px;
gap: clamp(16px,2.6vw,32px);
padding: clamp(20px,3vw,30px);
background: var(--sn-16-surface);
border-bottom: 1px solid var(--sn-16-line);
box-shadow: 0 30px 44px -30px oklch(0.23 0.024 55/.5);
max-block-size: min(62svh,420px);
overflow: auto;
opacity: 1;
translate: 0 0;
transition: opacity .22s ease,translate .26s cubic-bezier(.32,.72,.3,1);
}
@starting-style {
.sn-16__mega {
opacity: 0;
translate: 0 -10px;
}
}
.sn-16__mega[hidden] {
display: none;
}
.sn-16__col {
display: grid;
align-content: start;
gap: 3px;
min-width: 0;
}
.sn-16__ch {
margin-block-end: 7px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10.5px;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__mlink {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 10px;
margin-inline: -10px;
border-radius: 8px;
font-size: 14px;
font-weight: 560;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .16s ease,background .16s ease;
}
.sn-16__mlink:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__promo {
position: relative;
display: grid;
align-content: end;
gap: 4px;
min-block-size: 160px;
padding: 16px;
border-radius: 12px;
overflow: hidden;
color: oklch(1 0 0);
text-decoration: none;
background: linear-gradient(160deg,oklch(0.5 0.1 45),oklch(0.35 0.06 60));
}
.sn-16__promoimg {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
opacity: .62;
transition: scale .5s cubic-bezier(.32,.72,.3,1);
}
.sn-16__promo:hover .sn-16__promoimg {
scale: 1.06;
}
.sn-16__promotag,
.sn-16__promoh {
position: relative;
}
.sn-16__promotag {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
opacity: .92;
}
.sn-16__promoh {
font-size: 17px;
font-weight: 680;
letter-spacing: -.02em;
text-shadow: 0 1px 12px oklch(0 0 0/.5);
}
.sn-16__sec {
scroll-margin-top: var(--sn-16-h);
display: grid;
gap: 10px;
align-content: center;
min-height: 66svh;
padding: clamp(24px,4vw,48px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__sec:nth-child(even) {
background: oklch(0.975 0.008 70);
}
.sn-16__kicker {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__h {
font-size: clamp(19px,2.6vw,26px);
line-height: 1.16;
letter-spacing: -.025em;
font-weight: 690;
text-wrap: balance;
}
.sn-16__p {
max-width: 54ch;
font-size: 14.8px;
line-height: 1.64;
color: var(--sn-16-mut);
text-wrap: pretty;
}
@media (max-width: 780px) {
.sn-16__mega {
grid-template-columns: repeat(2,minmax(0,1fr));
}
.sn-16__promo {
grid-column: 1/-1;
min-block-size: 120px;
}
}
@media (max-width: 520px) {
.sn-16__mega {
grid-template-columns: 1fr;
}
.sn-16__link {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.sn-16 * {
transition-duration: .01ms !important;
}
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */
:root:has(.sn-16) {
scroll-behavior: smooth;
scroll-padding-top: calc(var(--sn-16-h) + 8px);
}
@media (prefers-reduced-motion: reduce) {
:root:has(.sn-16) {
scroll-behavior: auto;
}
}
.sn-16__bar,
.sn-16__barin {
padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}
.sn-16__sec {
padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}
.sn-16__pagefoot {
display: grid;
place-items: center;
padding: clamp(44px,9vh,110px) 24px;
border-top: 1px solid var(--sn-16-line);
}
.sn-16__pagefootin {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
letter-spacing: .06em;
color: var(--sn-16-mut);
text-align: center;
}
.sn-16__intro-title,
.sn-16__intro-sub {
text-wrap: balance;
}(() => {
const root = document.querySelector('.sn-16');
if (!root || root.dataset.snWired) return;
root.dataset.snWired = '1';
const bar = root.querySelector('#sn-16-bar');
const trigger = root.querySelector('#sn-16-trigger');
const mega = root.querySelector('#sn-16-mega');
let timer;
const set = (open) => {
clearTimeout(timer);
trigger.setAttribute('aria-expanded', String(open));
mega.hidden = !open;
};
trigger.addEventListener('click', () => set(trigger.getAttribute('aria-expanded') !== 'true'));
trigger.addEventListener('pointerenter', () => set(true));
bar.addEventListener('focusin', (e) => { if (e.target === trigger) set(true); });
bar.addEventListener('pointerleave', () => { timer = setTimeout(() => set(false), 160); });
bar.addEventListener('pointerenter', () => clearTimeout(timer));
bar.addEventListener('focusout', (e) => { if (!bar.contains(e.relatedTarget)) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && trigger.getAttribute('aria-expanded') === 'true') { set(false); trigger.focus(); }
});
mega.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
})();(() => {
const root = document.querySelector('.sn-16');
if (!root || root.dataset.snWired) return;
root.dataset.snWired = '1';
const bar = root.querySelector('#sn-16-bar');
const trigger = root.querySelector('#sn-16-trigger');
const mega = root.querySelector('#sn-16-mega');
let timer;
const set = (open) => {
clearTimeout(timer);
trigger.setAttribute('aria-expanded', String(open));
mega.hidden = !open;
};
trigger.addEventListener('click', () => set(trigger.getAttribute('aria-expanded') !== 'true'));
trigger.addEventListener('pointerenter', () => set(true));
bar.addEventListener('focusin', (e) => { if (e.target === trigger) set(true); });
bar.addEventListener('pointerleave', () => { timer = setTimeout(() => set(false), 160); });
bar.addEventListener('pointerenter', () => clearTimeout(timer));
bar.addEventListener('focusout', (e) => { if (!bar.contains(e.relatedTarget)) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && trigger.getAttribute('aria-expanded') === 'true') { set(false); trigger.focus(); }
});
mega.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
})();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: Sticky Header with Multi-Column Mega Menu Dropdown
Source: https://codefronts.com/navigation/css-sticky-navigation/sticky-header-with-multi-column-mega-menu-dropdown/
Category navigation for a catalogue that does not fit in a list: a full-width panel with grouped columns and a promoted product, opening from a sticky header so it stays reachable at any scroll depth. Built as a disclosure rather than a menu, which is what lets it work identically for pointer, keyboard and touch.
## HTML
```html
<section class="sn-16" aria-label="Multi-column mega menu demo">
<header class="sn-16__bar" id="sn-16-bar">
<a class="sn-16__brand" href="#sn-16-s1"><span class="sn-16__logo" aria-hidden="true"></span>Forge&Field</a>
<nav class="sn-16__nav" aria-label="Primary">
<button class="sn-16__trigger" type="button" id="sn-16-trigger" aria-expanded="false" aria-controls="sn-16-mega">
Shop
<svg class="sn-16__caret" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M2.5 4.5 6 8l3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<a class="sn-16__link" href="#sn-16-s2">Workshop</a>
<a class="sn-16__link" href="#sn-16-s3">Stories</a>
</nav>
<a class="sn-16__cta" href="#sn-16-s3">Bag</a>
<div class="sn-16__mega" id="sn-16-mega" hidden>
<div class="sn-16__col">
<h3 class="sn-16__ch">Hand tools</h3>
<a class="sn-16__mlink" href="#sn-16-s1">Chisels & gouges</a>
<a class="sn-16__mlink" href="#sn-16-s1">Planes</a>
<a class="sn-16__mlink" href="#sn-16-s1">Marking & measuring</a>
<a class="sn-16__mlink" href="#sn-16-s1">Sharpening</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Workshop</h3>
<a class="sn-16__mlink" href="#sn-16-s2">Benches</a>
<a class="sn-16__mlink" href="#sn-16-s2">Vices & clamps</a>
<a class="sn-16__mlink" href="#sn-16-s2">Dust extraction</a>
<a class="sn-16__mlink" href="#sn-16-s2">Storage</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Materials</h3>
<a class="sn-16__mlink" href="#sn-16-s3">Hardwoods</a>
<a class="sn-16__mlink" href="#sn-16-s3">Finishes & oils</a>
<a class="sn-16__mlink" href="#sn-16-s3">Adhesives</a>
<a class="sn-16__mlink" href="#sn-16-s3">Abrasives</a>
</div>
<a class="sn-16__promo" href="#sn-16-s1">
<img class="sn-16__promoimg" src="https://images.unsplash.com/photo-1504148455328-c376907d081c?q=80&w=800&auto=format&fit=crop" alt="Woodworking hand tools arranged on a workbench" width="800" height="600" loading="lazy" decoding="async">
<span class="sn-16__promotag">New season</span>
<span class="sn-16__promoh">The bench collection</span>
</a>
</div>
</header>
<main>
<section class="sn-16__sec" id="sn-16-s1">
<p class="sn-16__kicker">Disclosure, not menu</p>
<h3 class="sn-16__h">Links should behave like links</h3>
<p class="sn-16__p">An ARIA menu changes the keyboard contract to arrow-keys-only and announces items as application commands. A disclosure keeps Tab, keeps link semantics and works on touch.</p>
</section>
<section class="sn-16__sec" id="sn-16-s2">
<p class="sn-16__kicker">Grace period</p>
<h3 class="sn-16__h">Diagonal mouse paths</h3>
<p class="sn-16__p">Closing the instant the pointer leaves the trigger breaks the natural diagonal travel toward the panel. A short delay makes it feel forgiving without feeling stuck.</p>
</section>
<section class="sn-16__sec" id="sn-16-s3">
<p class="sn-16__kicker">Sticky context</p>
<h3 class="sn-16__h">The panel travels with the bar</h3>
<p class="sn-16__p">Absolutely positioned inside the sticky header, the panel needs no coordinate maths — it is pinned wherever the header is.</p>
</section>
</main>
<footer class="sn-16__pagefoot">
<p class="sn-16__pagefootin">Sticky navigation pattern 16 of 25 · codefronts.com</p>
</footer>
</section>
```
## CSS
```css
.sn-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--sn-16-bg);
--sn-16-bg: oklch(0.95 0.012 75);
--sn-16-surface: oklch(0.995 0.004 75);
--sn-16-ink: oklch(0.23 0.024 55);
--sn-16-mut: oklch(0.53 0.022 55);
--sn-16-acc: oklch(0.5 0.13 42);
--sn-16-line: oklch(0.23 0.024 55/.13);
--sn-16-h: 66px;
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--sn-16-ink);
-webkit-font-smoothing: antialiased;
}
.sn-16 *,
.sn-16 *::before,
.sn-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sn-16__bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: clamp(10px,2vw,24px);
min-height: var(--sn-16-h);
padding: 0 clamp(14px,2.6vw,24px);
background: color-mix(in oklch,var(--sn-16-surface) 90%,transparent);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 15px;
font-weight: 700;
letter-spacing: -.025em;
color: inherit;
text-decoration: none;
white-space: nowrap;
}
.sn-16__logo {
width: 20px;
height: 20px;
border-radius: 5px;
background: var(--sn-16-acc);
clip-path: polygon(0 20%,100% 0,80% 100%,20% 80%);
}
.sn-16__nav {
display: flex;
align-items: center;
gap: 2px;
margin-inline-start: auto;
}
.sn-16__trigger {
display: flex;
align-items: center;
gap: 6px;
min-height: 38px;
padding: 0 12px;
border: 0;
border-radius: 9px;
background: transparent;
font-family: inherit;
font-size: 13.6px;
font-weight: 640;
color: var(--sn-16-mut);
cursor: pointer;
transition: color .18s ease,background .18s ease;
}
.sn-16__trigger:hover,
.sn-16__trigger[aria-expanded="true"] {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.06);
}
.sn-16__caret {
inline-size: 12px;
block-size: 12px;
transition: rotate .26s ease;
}
.sn-16__trigger[aria-expanded="true"] .sn-16__caret {
rotate: 180deg;
}
.sn-16__link {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 12px;
border-radius: 9px;
font-size: 13.6px;
font-weight: 600;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .18s ease,background .18s ease;
}
.sn-16__link:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__cta {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 16px;
border-radius: 99px;
font-size: 13.2px;
font-weight: 660;
color: oklch(1 0 0);
background: var(--sn-16-acc);
text-decoration: none;
white-space: nowrap;
transition: transform .18s ease;
}
.sn-16__cta:hover {
transform: translateY(-1px);
}
.sn-16__trigger:focus-visible,
.sn-16__link:focus-visible,
.sn-16__cta:focus-visible,
.sn-16__brand:focus-visible,
.sn-16__mlink:focus-visible,
.sn-16__promo:focus-visible {
outline: 2px solid var(--sn-16-acc);
outline-offset: 2px;
}
.sn-16__mega {
position: absolute;
inset-inline: 0;
top: 100%;
display: grid;
grid-template-columns: repeat(3,minmax(0,1fr)) 250px;
gap: clamp(16px,2.6vw,32px);
padding: clamp(20px,3vw,30px);
background: var(--sn-16-surface);
border-bottom: 1px solid var(--sn-16-line);
box-shadow: 0 30px 44px -30px oklch(0.23 0.024 55/.5);
max-block-size: min(62svh,420px);
overflow: auto;
opacity: 1;
translate: 0 0;
transition: opacity .22s ease,translate .26s cubic-bezier(.32,.72,.3,1);
}
@starting-style {
.sn-16__mega {
opacity: 0;
translate: 0 -10px;
}
}
.sn-16__mega[hidden] {
display: none;
}
.sn-16__col {
display: grid;
align-content: start;
gap: 3px;
min-width: 0;
}
.sn-16__ch {
margin-block-end: 7px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10.5px;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__mlink {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 10px;
margin-inline: -10px;
border-radius: 8px;
font-size: 14px;
font-weight: 560;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .16s ease,background .16s ease;
}
.sn-16__mlink:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__promo {
position: relative;
display: grid;
align-content: end;
gap: 4px;
min-block-size: 160px;
padding: 16px;
border-radius: 12px;
overflow: hidden;
color: oklch(1 0 0);
text-decoration: none;
background: linear-gradient(160deg,oklch(0.5 0.1 45),oklch(0.35 0.06 60));
}
.sn-16__promoimg {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
opacity: .62;
transition: scale .5s cubic-bezier(.32,.72,.3,1);
}
.sn-16__promo:hover .sn-16__promoimg {
scale: 1.06;
}
.sn-16__promotag,
.sn-16__promoh {
position: relative;
}
.sn-16__promotag {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
opacity: .92;
}
.sn-16__promoh {
font-size: 17px;
font-weight: 680;
letter-spacing: -.02em;
text-shadow: 0 1px 12px oklch(0 0 0/.5);
}
.sn-16__sec {
scroll-margin-top: var(--sn-16-h);
display: grid;
gap: 10px;
align-content: center;
min-height: 66svh;
padding: clamp(24px,4vw,48px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__sec:nth-child(even) {
background: oklch(0.975 0.008 70);
}
.sn-16__kicker {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__h {
font-size: clamp(19px,2.6vw,26px);
line-height: 1.16;
letter-spacing: -.025em;
font-weight: 690;
text-wrap: balance;
}
.sn-16__p {
max-width: 54ch;
font-size: 14.8px;
line-height: 1.64;
color: var(--sn-16-mut);
text-wrap: pretty;
}
@media (max-width: 780px) {
.sn-16__mega {
grid-template-columns: repeat(2,minmax(0,1fr));
}
.sn-16__promo {
grid-column: 1/-1;
min-block-size: 120px;
}
}
@media (max-width: 520px) {
.sn-16__mega {
grid-template-columns: 1fr;
}
.sn-16__link {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.sn-16 * {
transition-duration: .01ms !important;
}
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */
:root:has(.sn-16) {
scroll-behavior: smooth;
scroll-padding-top: calc(var(--sn-16-h) + 8px);
}
@media (prefers-reduced-motion: reduce) {
:root:has(.sn-16) {
scroll-behavior: auto;
}
}
.sn-16__bar,
.sn-16__barin {
padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}
.sn-16__sec {
padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}
.sn-16__pagefoot {
display: grid;
place-items: center;
padding: clamp(44px,9vh,110px) 24px;
border-top: 1px solid var(--sn-16-line);
}
.sn-16__pagefootin {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
letter-spacing: .06em;
color: var(--sn-16-mut);
text-align: center;
}
.sn-16__intro-title,
.sn-16__intro-sub {
text-wrap: balance;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.sn-16');
if (!root || root.dataset.snWired) return;
root.dataset.snWired = '1';
const bar = root.querySelector('#sn-16-bar');
const trigger = root.querySelector('#sn-16-trigger');
const mega = root.querySelector('#sn-16-mega');
let timer;
const set = (open) => {
clearTimeout(timer);
trigger.setAttribute('aria-expanded', String(open));
mega.hidden = !open;
};
trigger.addEventListener('click', () => set(trigger.getAttribute('aria-expanded') !== 'true'));
trigger.addEventListener('pointerenter', () => set(true));
bar.addEventListener('focusin', (e) => { if (e.target === trigger) set(true); });
bar.addEventListener('pointerleave', () => { timer = setTimeout(() => set(false), 160); });
bar.addEventListener('pointerenter', () => clearTimeout(timer));
bar.addEventListener('focusout', (e) => { if (!bar.contains(e.relatedTarget)) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && trigger.getAttribute('aria-expanded') === 'true') { set(false); trigger.focus(); }
});
mega.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
})();
```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: Sticky Header with Multi-Column Mega Menu Dropdown
Source: https://codefronts.com/navigation/css-sticky-navigation/sticky-header-with-multi-column-mega-menu-dropdown/
Category navigation for a catalogue that does not fit in a list: a full-width panel with grouped columns and a promoted product, opening from a sticky header so it stays reachable at any scroll depth. Built as a disclosure rather than a menu, which is what lets it work identically for pointer, keyboard and touch.
## HTML
```html
<section class="sn-16" aria-label="Multi-column mega menu demo">
<header class="sn-16__bar" id="sn-16-bar">
<a class="sn-16__brand" href="#sn-16-s1"><span class="sn-16__logo" aria-hidden="true"></span>Forge&Field</a>
<nav class="sn-16__nav" aria-label="Primary">
<button class="sn-16__trigger" type="button" id="sn-16-trigger" aria-expanded="false" aria-controls="sn-16-mega">
Shop
<svg class="sn-16__caret" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="M2.5 4.5 6 8l3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<a class="sn-16__link" href="#sn-16-s2">Workshop</a>
<a class="sn-16__link" href="#sn-16-s3">Stories</a>
</nav>
<a class="sn-16__cta" href="#sn-16-s3">Bag</a>
<div class="sn-16__mega" id="sn-16-mega" hidden>
<div class="sn-16__col">
<h3 class="sn-16__ch">Hand tools</h3>
<a class="sn-16__mlink" href="#sn-16-s1">Chisels & gouges</a>
<a class="sn-16__mlink" href="#sn-16-s1">Planes</a>
<a class="sn-16__mlink" href="#sn-16-s1">Marking & measuring</a>
<a class="sn-16__mlink" href="#sn-16-s1">Sharpening</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Workshop</h3>
<a class="sn-16__mlink" href="#sn-16-s2">Benches</a>
<a class="sn-16__mlink" href="#sn-16-s2">Vices & clamps</a>
<a class="sn-16__mlink" href="#sn-16-s2">Dust extraction</a>
<a class="sn-16__mlink" href="#sn-16-s2">Storage</a>
</div>
<div class="sn-16__col">
<h3 class="sn-16__ch">Materials</h3>
<a class="sn-16__mlink" href="#sn-16-s3">Hardwoods</a>
<a class="sn-16__mlink" href="#sn-16-s3">Finishes & oils</a>
<a class="sn-16__mlink" href="#sn-16-s3">Adhesives</a>
<a class="sn-16__mlink" href="#sn-16-s3">Abrasives</a>
</div>
<a class="sn-16__promo" href="#sn-16-s1">
<img class="sn-16__promoimg" src="https://images.unsplash.com/photo-1504148455328-c376907d081c?q=80&w=800&auto=format&fit=crop" alt="Woodworking hand tools arranged on a workbench" width="800" height="600" loading="lazy" decoding="async">
<span class="sn-16__promotag">New season</span>
<span class="sn-16__promoh">The bench collection</span>
</a>
</div>
</header>
<main>
<section class="sn-16__sec" id="sn-16-s1">
<p class="sn-16__kicker">Disclosure, not menu</p>
<h3 class="sn-16__h">Links should behave like links</h3>
<p class="sn-16__p">An ARIA menu changes the keyboard contract to arrow-keys-only and announces items as application commands. A disclosure keeps Tab, keeps link semantics and works on touch.</p>
</section>
<section class="sn-16__sec" id="sn-16-s2">
<p class="sn-16__kicker">Grace period</p>
<h3 class="sn-16__h">Diagonal mouse paths</h3>
<p class="sn-16__p">Closing the instant the pointer leaves the trigger breaks the natural diagonal travel toward the panel. A short delay makes it feel forgiving without feeling stuck.</p>
</section>
<section class="sn-16__sec" id="sn-16-s3">
<p class="sn-16__kicker">Sticky context</p>
<h3 class="sn-16__h">The panel travels with the bar</h3>
<p class="sn-16__p">Absolutely positioned inside the sticky header, the panel needs no coordinate maths — it is pinned wherever the header is.</p>
</section>
</main>
<footer class="sn-16__pagefoot">
<p class="sn-16__pagefootin">Sticky navigation pattern 16 of 25 · codefronts.com</p>
</footer>
</section>
```
## CSS
```css
.sn-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--sn-16-bg);
--sn-16-bg: oklch(0.95 0.012 75);
--sn-16-surface: oklch(0.995 0.004 75);
--sn-16-ink: oklch(0.23 0.024 55);
--sn-16-mut: oklch(0.53 0.022 55);
--sn-16-acc: oklch(0.5 0.13 42);
--sn-16-line: oklch(0.23 0.024 55/.13);
--sn-16-h: 66px;
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--sn-16-ink);
-webkit-font-smoothing: antialiased;
}
.sn-16 *,
.sn-16 *::before,
.sn-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sn-16__bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: clamp(10px,2vw,24px);
min-height: var(--sn-16-h);
padding: 0 clamp(14px,2.6vw,24px);
background: color-mix(in oklch,var(--sn-16-surface) 90%,transparent);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 15px;
font-weight: 700;
letter-spacing: -.025em;
color: inherit;
text-decoration: none;
white-space: nowrap;
}
.sn-16__logo {
width: 20px;
height: 20px;
border-radius: 5px;
background: var(--sn-16-acc);
clip-path: polygon(0 20%,100% 0,80% 100%,20% 80%);
}
.sn-16__nav {
display: flex;
align-items: center;
gap: 2px;
margin-inline-start: auto;
}
.sn-16__trigger {
display: flex;
align-items: center;
gap: 6px;
min-height: 38px;
padding: 0 12px;
border: 0;
border-radius: 9px;
background: transparent;
font-family: inherit;
font-size: 13.6px;
font-weight: 640;
color: var(--sn-16-mut);
cursor: pointer;
transition: color .18s ease,background .18s ease;
}
.sn-16__trigger:hover,
.sn-16__trigger[aria-expanded="true"] {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.06);
}
.sn-16__caret {
inline-size: 12px;
block-size: 12px;
transition: rotate .26s ease;
}
.sn-16__trigger[aria-expanded="true"] .sn-16__caret {
rotate: 180deg;
}
.sn-16__link {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 12px;
border-radius: 9px;
font-size: 13.6px;
font-weight: 600;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .18s ease,background .18s ease;
}
.sn-16__link:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__cta {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 16px;
border-radius: 99px;
font-size: 13.2px;
font-weight: 660;
color: oklch(1 0 0);
background: var(--sn-16-acc);
text-decoration: none;
white-space: nowrap;
transition: transform .18s ease;
}
.sn-16__cta:hover {
transform: translateY(-1px);
}
.sn-16__trigger:focus-visible,
.sn-16__link:focus-visible,
.sn-16__cta:focus-visible,
.sn-16__brand:focus-visible,
.sn-16__mlink:focus-visible,
.sn-16__promo:focus-visible {
outline: 2px solid var(--sn-16-acc);
outline-offset: 2px;
}
.sn-16__mega {
position: absolute;
inset-inline: 0;
top: 100%;
display: grid;
grid-template-columns: repeat(3,minmax(0,1fr)) 250px;
gap: clamp(16px,2.6vw,32px);
padding: clamp(20px,3vw,30px);
background: var(--sn-16-surface);
border-bottom: 1px solid var(--sn-16-line);
box-shadow: 0 30px 44px -30px oklch(0.23 0.024 55/.5);
max-block-size: min(62svh,420px);
overflow: auto;
opacity: 1;
translate: 0 0;
transition: opacity .22s ease,translate .26s cubic-bezier(.32,.72,.3,1);
}
@starting-style {
.sn-16__mega {
opacity: 0;
translate: 0 -10px;
}
}
.sn-16__mega[hidden] {
display: none;
}
.sn-16__col {
display: grid;
align-content: start;
gap: 3px;
min-width: 0;
}
.sn-16__ch {
margin-block-end: 7px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10.5px;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__mlink {
display: flex;
align-items: center;
min-height: 38px;
padding: 0 10px;
margin-inline: -10px;
border-radius: 8px;
font-size: 14px;
font-weight: 560;
color: var(--sn-16-mut);
text-decoration: none;
transition: color .16s ease,background .16s ease;
}
.sn-16__mlink:hover {
color: var(--sn-16-ink);
background: oklch(0.23 0.024 55/.05);
}
.sn-16__promo {
position: relative;
display: grid;
align-content: end;
gap: 4px;
min-block-size: 160px;
padding: 16px;
border-radius: 12px;
overflow: hidden;
color: oklch(1 0 0);
text-decoration: none;
background: linear-gradient(160deg,oklch(0.5 0.1 45),oklch(0.35 0.06 60));
}
.sn-16__promoimg {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
opacity: .62;
transition: scale .5s cubic-bezier(.32,.72,.3,1);
}
.sn-16__promo:hover .sn-16__promoimg {
scale: 1.06;
}
.sn-16__promotag,
.sn-16__promoh {
position: relative;
}
.sn-16__promotag {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
opacity: .92;
}
.sn-16__promoh {
font-size: 17px;
font-weight: 680;
letter-spacing: -.02em;
text-shadow: 0 1px 12px oklch(0 0 0/.5);
}
.sn-16__sec {
scroll-margin-top: var(--sn-16-h);
display: grid;
gap: 10px;
align-content: center;
min-height: 66svh;
padding: clamp(24px,4vw,48px);
border-bottom: 1px solid var(--sn-16-line);
}
.sn-16__sec:nth-child(even) {
background: oklch(0.975 0.008 70);
}
.sn-16__kicker {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--sn-16-acc);
}
.sn-16__h {
font-size: clamp(19px,2.6vw,26px);
line-height: 1.16;
letter-spacing: -.025em;
font-weight: 690;
text-wrap: balance;
}
.sn-16__p {
max-width: 54ch;
font-size: 14.8px;
line-height: 1.64;
color: var(--sn-16-mut);
text-wrap: pretty;
}
@media (max-width: 780px) {
.sn-16__mega {
grid-template-columns: repeat(2,minmax(0,1fr));
}
.sn-16__promo {
grid-column: 1/-1;
min-block-size: 120px;
}
}
@media (max-width: 520px) {
.sn-16__mega {
grid-template-columns: 1fr;
}
.sn-16__link {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.sn-16 * {
transition-duration: .01ms !important;
}
}
/* ── full-page mode: the demo IS the document, so sticky pins against the real viewport ── */
:root:has(.sn-16) {
scroll-behavior: smooth;
scroll-padding-top: calc(var(--sn-16-h) + 8px);
}
@media (prefers-reduced-motion: reduce) {
:root:has(.sn-16) {
scroll-behavior: auto;
}
}
.sn-16__bar,
.sn-16__barin {
padding-inline: max(clamp(16px,3vw,26px),calc((100% - 1280px)/2));
}
.sn-16__sec {
padding-inline: max(clamp(22px,5vw,44px),calc((100% - 1120px)/2));
}
.sn-16__pagefoot {
display: grid;
place-items: center;
padding: clamp(44px,9vh,110px) 24px;
border-top: 1px solid var(--sn-16-line);
}
.sn-16__pagefootin {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
letter-spacing: .06em;
color: var(--sn-16-mut);
text-align: center;
}
.sn-16__intro-title,
.sn-16__intro-sub {
text-wrap: balance;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.sn-16');
if (!root || root.dataset.snWired) return;
root.dataset.snWired = '1';
const bar = root.querySelector('#sn-16-bar');
const trigger = root.querySelector('#sn-16-trigger');
const mega = root.querySelector('#sn-16-mega');
let timer;
const set = (open) => {
clearTimeout(timer);
trigger.setAttribute('aria-expanded', String(open));
mega.hidden = !open;
};
trigger.addEventListener('click', () => set(trigger.getAttribute('aria-expanded') !== 'true'));
trigger.addEventListener('pointerenter', () => set(true));
bar.addEventListener('focusin', (e) => { if (e.target === trigger) set(true); });
bar.addEventListener('pointerleave', () => { timer = setTimeout(() => set(false), 160); });
bar.addEventListener('pointerenter', () => clearTimeout(timer));
bar.addEventListener('focusout', (e) => { if (!bar.contains(e.relatedTarget)) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && trigger.getAttribute('aria-expanded') === 'true') { set(false); trigger.focus(); }
});
mega.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
})();
```How this works
The panel is a grid that spans the header's full width and drops beneath it:
.mega{
position:absolute; inset-inline:0; top:100%;
display:grid;
grid-template-columns: repeat(3, minmax(0,1fr)) 260px; /* 3 lists + promo */
gap: 28px;
}
.mega[hidden]{ display:none }
@starting-style{ .mega{ opacity:0; translate:0 -8px } } /* entry from nothing */Because the trigger sits in a sticky header, the panel inherits the header's stacking context and stays pinned with it — no fixed positioning and no manual coordinate maths as the page scrolls.
The interaction model is the important decision. This is not a role="menu": ARIA menus imply desktop-application semantics where arrow keys are the only way to move and Tab exits the whole widget, which is wrong for a page of links. Treating it as a disclosure — a <button aria-expanded> revealing a region of ordinary links — means Tab walks the links naturally, screen readers announce them as links, and touch users get a straightforward tap.
Hover opens it for pointer users, but hover alone would strand keyboards, so the same open state is driven by focusin and by click. Closing is handled three ways: Escape, focus leaving the header, and pointer leaving with a short grace delay so a diagonal mouse path across the gap does not slam the panel shut mid-travel.
Make it yours
- Column count: one
grid-template-columnsvalue. Drop the promo track for a pure link matrix, or make it two promos on wide screens. - Per-category panels: give each trigger its own panel id and reuse the same script — it keys off
aria-controls. - Full-bleed backdrop: extend the panel to the viewport edges and centre its content on the page grid from demo 04.
- Anchor positioning: with
anchor-nameandposition-anchorthe panel can tether to its trigger instead of the header, which suits menus that are not full width.
Gotchas — read before shipping
role="menu"on a set of navigation links makes screen readers announce them as application menu items and changes the expected keys. Use a disclosure button plus a plain link list.- Hover-only mega menus are unusable by keyboard and unreliable on touch, where the first tap becomes a hover and the second follows the link.
- Closing instantly on
pointerleavemakes diagonal mouse paths fail. A 120–200ms grace period fixes it without feeling sticky. - A panel hidden with
opacity:0alone keeps its links focusable. Use thehiddenattribute orvisibility. - An absolutely-positioned panel inside an ancestor with
overflow:hiddengets clipped — a very common failure when the header lives inside a wrapper with a rounded radius. - Long category lists inside a sticky header can exceed the viewport height. Cap the panel with
max-block-sizeand let it scroll internally.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 117+ | 17.5+ | 129+ | 117+ |
@starting-style shipped in Chrome 117, Safari 17.5 and Firefox 129; where it is missing the panel simply appears without its entry animation. Everything else here is universally supported.