17 / 20
All 20 in 20 CSS Mega Menus
- 01Mega Menu Reference Build with Focus Within
- 02Hover Intent Delay to Stop Panel Flicker
- 03The Safe Triangle Diagonal Pointer Problem
- 04Mega Menu Column Layout with CSS Grid
- 05Panel Positioning and Viewport Edge Collision
- 06Full Bleed Panel Inside a Constrained Header
- 07Keyboard Accessible Mega Menu with Escape
- 08Arrow Key Navigation Across Menu Columns
- 09ARIA for Disclosure Style Navigation Panels
- 10Mega Menu Touch and Double Tap Behavior
- 11Screen Reader Announcements on Panel Open
- 12Shared Panel That Swaps Content Between Items
- 13Mega Menu Entry Transition with Starting Style
- 14How a Mega Menu Should Collapse on Mobile
- 15Editorial Mega Menu with Featured Image
- 16Dashboard Mega Menu with Live Status Panel
- 17Mega Menu with Category Image Cards
- 18Department Store Mega Menu Grid
- 19Product Finder Mega Menu with Filters
- 20Promotional Mega Menu with Offer Panel
Mega Menu with Category Image Cards
A category grid where each destination is an image card rather than a text link, for the case where the picture carries more information than the label. The demo is built around image loading: an unsized image inside a panel that opens on hover causes a visible reflow the first time it appears, so every card reserves its box before the file arrives.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<section class="mm-17" aria-labelledby="mm-17-title">
<h2 class="mm-17__sr" id="mm-17-title">Fern Studio</h2>
<div class="mm-17__theme">
<input class="mm-17__theme-input" type="checkbox" id="mm-17-theme">
<label class="mm-17__theme-label" for="mm-17-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="mm-17__sr">Toggle dark theme</span>
</label>
</div>
<header class="mm-17__header">
<div class="mm-17__bar">
<a class="mm-17__brand" href="#mm-17-title">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 21c0-7 4-12 9-13-1 8-4 12-9 13Z" fill="currentColor" opacity=".85"/><path d="M12 21C9 15 5 12 3 11c1 6 4 9 9 10Z" fill="currentColor" opacity=".5"/></svg>
Fern Studio
</a>
<nav class="mm-17__nav" aria-label="Primary">
<ul class="mm-17__list">
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-shop" aria-expanded="false" aria-controls="mm-17-panel-shop">Shop by room</button>
<div class="mm-17__panel" id="mm-17-panel-shop" aria-labelledby="mm-17-trigger-shop">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-living/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Living room</span>
<span class="mm-17__card-meta">34 pieces · low light</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-kitchen/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Kitchen</span>
<span class="mm-17__card-meta">21 pieces · herbs</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-studio/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Workspace</span>
<span class="mm-17__card-meta">18 pieces · desk scale</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-bath/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Bathroom</span>
<span class="mm-17__card-meta">12 pieces · humidity</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-balcony/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Balcony</span>
<span class="mm-17__card-meta">27 pieces · hardy</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-hall/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Hallway</span>
<span class="mm-17__card-meta">9 pieces · shade</span>
</a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Care and setup</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Light and watering guide</a></li>
<li><a href="#mm-17-title">Repotting service</a></li>
<li><a href="#mm-17-title">Studio consultations</a></li>
<li><a href="#mm-17-title">Plant doctor</a></li>
</ul>
<a class="mm-17__aside-cta" href="#mm-17-title">Book a home visit</a>
</div>
</div>
</div>
</li>
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-pots" aria-expanded="false" aria-controls="mm-17-panel-pots">Pots & care</button>
<div class="mm-17__panel" id="mm-17-panel-pots" aria-labelledby="mm-17-trigger-pots">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-terracotta/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Terracotta</span><span class="mm-17__card-meta">6 sizes</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stoneware/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stoneware</span><span class="mm-17__card-meta">glazed</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stands/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stands</span><span class="mm-17__card-meta">oak and steel</span></a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Essentials</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Soil and grit</a></li>
<li><a href="#mm-17-title">Feed</a></li>
<li><a href="#mm-17-title">Tools</a></li>
</ul>
</div>
</div>
</div>
</li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Journal</a></li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Studio</a></li>
</ul>
</nav>
<div class="mm-17__utils">
<a class="mm-17__util" href="#mm-17-title">Account</a>
<a class="mm-17__bag" href="#mm-17-title">Basket · 2</a>
</div>
</div>
</header>
<main class="mm-17__page">
<div class="mm-17__hero">
<p class="mm-17__kicker">Delivered in 3 days, potted and fed</p>
<h1 class="mm-17__h1">Plants chosen for the light you actually have.</h1>
<a class="mm-17__cta" href="#mm-17-title">Take the light quiz</a>
</div>
</main>
</section><section class="mm-17" aria-labelledby="mm-17-title">
<h2 class="mm-17__sr" id="mm-17-title">Fern Studio</h2>
<div class="mm-17__theme">
<input class="mm-17__theme-input" type="checkbox" id="mm-17-theme">
<label class="mm-17__theme-label" for="mm-17-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="mm-17__sr">Toggle dark theme</span>
</label>
</div>
<header class="mm-17__header">
<div class="mm-17__bar">
<a class="mm-17__brand" href="#mm-17-title">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 21c0-7 4-12 9-13-1 8-4 12-9 13Z" fill="currentColor" opacity=".85"/><path d="M12 21C9 15 5 12 3 11c1 6 4 9 9 10Z" fill="currentColor" opacity=".5"/></svg>
Fern Studio
</a>
<nav class="mm-17__nav" aria-label="Primary">
<ul class="mm-17__list">
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-shop" aria-expanded="false" aria-controls="mm-17-panel-shop">Shop by room</button>
<div class="mm-17__panel" id="mm-17-panel-shop" aria-labelledby="mm-17-trigger-shop">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-living/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Living room</span>
<span class="mm-17__card-meta">34 pieces · low light</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-kitchen/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Kitchen</span>
<span class="mm-17__card-meta">21 pieces · herbs</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-studio/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Workspace</span>
<span class="mm-17__card-meta">18 pieces · desk scale</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-bath/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Bathroom</span>
<span class="mm-17__card-meta">12 pieces · humidity</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-balcony/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Balcony</span>
<span class="mm-17__card-meta">27 pieces · hardy</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-hall/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Hallway</span>
<span class="mm-17__card-meta">9 pieces · shade</span>
</a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Care and setup</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Light and watering guide</a></li>
<li><a href="#mm-17-title">Repotting service</a></li>
<li><a href="#mm-17-title">Studio consultations</a></li>
<li><a href="#mm-17-title">Plant doctor</a></li>
</ul>
<a class="mm-17__aside-cta" href="#mm-17-title">Book a home visit</a>
</div>
</div>
</div>
</li>
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-pots" aria-expanded="false" aria-controls="mm-17-panel-pots">Pots & care</button>
<div class="mm-17__panel" id="mm-17-panel-pots" aria-labelledby="mm-17-trigger-pots">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-terracotta/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Terracotta</span><span class="mm-17__card-meta">6 sizes</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stoneware/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stoneware</span><span class="mm-17__card-meta">glazed</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stands/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stands</span><span class="mm-17__card-meta">oak and steel</span></a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Essentials</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Soil and grit</a></li>
<li><a href="#mm-17-title">Feed</a></li>
<li><a href="#mm-17-title">Tools</a></li>
</ul>
</div>
</div>
</div>
</li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Journal</a></li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Studio</a></li>
</ul>
</nav>
<div class="mm-17__utils">
<a class="mm-17__util" href="#mm-17-title">Account</a>
<a class="mm-17__bag" href="#mm-17-title">Basket · 2</a>
</div>
</div>
</header>
<main class="mm-17__page">
<div class="mm-17__hero">
<p class="mm-17__kicker">Delivered in 3 days, potted and fed</p>
<h1 class="mm-17__h1">Plants chosen for the light you actually have.</h1>
<a class="mm-17__cta" href="#mm-17-title">Take the light quiz</a>
</div>
</main>
</section>.mm-17 {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
--mm-17-card-min: 11rem;
--mm-17-zoom: 1.06;
--mm-17-font-display: "Avenir Next", "Segoe UI", ui-rounded, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background-color: var(--mm-17-bg);
background-image: radial-gradient(64rem 32rem at 88% -8%, color-mix(in oklab, var(--mm-17-acc) 14%, transparent), transparent 62%);
color: var(--mm-17-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.mm-17 *,
.mm-17 *::before,
.mm-17 *::after {
box-sizing: border-box;
}
.mm-17 :focus-visible {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
border-radius: 12px;
}
.mm-17__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
@media (prefers-color-scheme: dark) {
.mm-17 {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
}
}
@media (prefers-color-scheme: light) {
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
}
.mm-17__theme {
position: absolute;
inset-block-start: 13px;
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 60;
}
.mm-17__theme-input {
position: absolute;
opacity: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
cursor: pointer;
}
.mm-17__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mm-17-line);
border-radius: 999px;
background: var(--mm-17-surf);
color: var(--mm-17-ink);
cursor: pointer;
box-shadow: 0 10px 26px -16px rgba(35, 38, 31, 0.7);
}
.mm-17__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mm-17__theme-input:focus-visible + .mm-17__theme-label {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
}
.mm-17__header {
position: relative;
z-index: 10;
inline-size: 100%;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
}
.mm-17__bar {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: flex;
align-items: center;
gap: clamp(0.5rem, 2vw, 2rem);
flex-wrap: wrap;
min-block-size: 70px;
padding-inline-end: 60px;
}
.mm-17__brand {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--mm-17-ink);
text-decoration: none;
font: 600 1.1rem/1 var(--mm-17-font-display);
letter-spacing: -0.01em;
}
.mm-17__brand svg {
inline-size: 22px;
block-size: 22px;
color: var(--mm-17-acc);
}
.mm-17__nav {
min-inline-size: 0;
}
.mm-17__list {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
}
.mm-17__item {
position: static;
}
.mm-17__trigger,
.mm-17__plain {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.9rem;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--mm-17-ink);
font: 600 0.92rem/1 var(--mm-17-font-display);
text-decoration: none;
cursor: pointer;
}
.mm-17__item--open > .mm-17__trigger,
.mm-17__item:hover > .mm-17__trigger,
.mm-17__item:focus-within > .mm-17__trigger {
background: color-mix(in oklab, var(--mm-17-acc) 16%, transparent);
color: var(--mm-17-acc);
}
.mm-17__plain:hover,
.mm-17__trigger:hover {
color: var(--mm-17-acc);
}
.mm-17__utils {
margin-inline-start: auto;
display: flex;
align-items: center;
gap: 0.5rem;
}
.mm-17__util {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.6rem;
color: var(--mm-17-mut);
text-decoration: none;
font: 600 0.86rem/1 var(--mm-17-font-display);
}
.mm-17__bag {
display: inline-flex;
align-items: center;
min-block-size: 40px;
padding-inline: 0.95rem;
border-radius: 999px;
border: 1px solid var(--mm-17-line);
color: var(--mm-17-ink);
text-decoration: none;
font: 600 0.84rem/1 var(--mm-17-font-display);
}
.mm-17__panel {
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
z-index: 30;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
box-shadow: 0 34px 70px -40px rgba(35, 38, 31, 0.55);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 180ms ease, visibility 0s linear 180ms;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
@media (hover: hover) {
.mm-17__item:hover > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
}
.mm-17__panel-inner {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1rem, 3vw, 2rem);
padding-block: clamp(1.1rem, 3vw, 2rem);
}
@media (min-width: 68rem) {
.mm-17__panel-inner {
grid-template-columns: minmax(0, 1fr) 17rem;
}
}
.mm-17__cards {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--mm-17-card-min), 1fr));
gap: clamp(0.6rem, 2vw, 1rem);
min-inline-size: 0;
}
.mm-17__card {
display: grid;
gap: 0.2rem;
text-decoration: none;
color: inherit;
min-inline-size: 0;
}
.mm-17__crop {
display: block;
overflow: hidden;
border-radius: 14px;
background: var(--mm-17-line);
aspect-ratio: 4 / 3;
}
.mm-17__crop img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
display: block;
transition: scale 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mm-17__card:hover .mm-17__crop img {
scale: var(--mm-17-zoom);
}
.mm-17__card-name {
margin-block-start: 0.35rem;
font: 600 0.95rem/1.3 var(--mm-17-font-display);
min-inline-size: 0;
}
.mm-17__card:hover .mm-17__card-name {
color: var(--mm-17-acc);
}
.mm-17__card-meta {
font-size: 0.76rem;
color: var(--mm-17-mut);
}
.mm-17__aside {
min-inline-size: 0;
display: grid;
align-content: start;
gap: 0.3rem;
padding: clamp(0.9rem, 2.4vw, 1.2rem);
border-radius: 16px;
background: color-mix(in oklab, var(--mm-17-acc) 9%, transparent);
}
.mm-17__aside-head {
margin: 0;
font: 700 0.68rem/1 var(--mm-17-font-display);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__links {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
}
.mm-17__links a {
display: flex;
align-items: center;
min-block-size: 44px;
color: var(--mm-17-ink);
text-decoration: none;
font-size: 0.9rem;
min-inline-size: 0;
}
.mm-17__links a:hover {
color: var(--mm-17-acc);
text-decoration: underline;
text-underline-offset: 3px;
}
.mm-17__aside-cta {
display: inline-flex;
align-items: center;
min-block-size: 44px;
margin-block-start: 0.2rem;
color: var(--mm-17-acc);
font: 700 0.86rem/1 var(--mm-17-font-display);
text-decoration: none;
}
.mm-17__page {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
padding-block: clamp(3rem, 9vw, 5.5rem);
}
.mm-17__hero {
max-inline-size: 34rem;
display: grid;
gap: 0.6rem;
}
.mm-17__kicker {
margin: 0;
font: 700 0.7rem/1 var(--mm-17-font-display);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__h1 {
margin: 0;
font: 600 clamp(2rem, 6vw, 3.4rem)/1.06 var(--mm-17-font-display);
letter-spacing: -0.025em;
text-wrap: balance;
}
.mm-17__cta {
justify-self: start;
display: inline-flex;
align-items: center;
min-block-size: 48px;
margin-block-start: 0.4rem;
padding-inline: 1.3rem;
border-radius: 999px;
background: var(--mm-17-acc);
color: var(--mm-17-on-acc);
text-decoration: none;
font: 700 0.92rem/1 var(--mm-17-font-display);
}
@media (max-width: 54rem) {
.mm-17__bar {
flex-direction: column;
align-items: stretch;
padding-block: 0.6rem;
}
.mm-17__list {
flex-direction: column;
}
.mm-17__utils {
margin-inline-start: 0;
}
.mm-17__panel {
position: static;
opacity: 1;
visibility: visible;
pointer-events: auto;
box-shadow: none;
display: none;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
display: block;
}
.mm-17__panel-inner {
inline-size: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.mm-17__panel,
.mm-17__crop img {
transition-duration: 1ms;
}
}
@media (hover: hover) and (pointer: fine) {
.mm-17__trigger {
position: relative;
}
.mm-17__item:hover > .mm-17__trigger::after,
.mm-17__item:focus-within > .mm-17__trigger::after {
content: "";
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
block-size: 16px;
}
}
/* Entry signature — slow organic settle; cards ease up over half a second, links follow on a long stagger */
.mm-17__card {
opacity: 0;
translate: 0 12px;
scale: 0.985;
transition: opacity 420ms ease, translate 580ms cubic-bezier(0.16, 1, 0.3, 1), scale 580ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-17__item:hover > .mm-17__panel .mm-17__card,
.mm-17__item:focus-within > .mm-17__panel .mm-17__card,
.mm-17__item--open > .mm-17__panel .mm-17__card {
opacity: 1;
translate: 0 0;
scale: 1;
}
.mm-17__card:nth-child(1) {
transition-delay: 40ms;
}
.mm-17__card:nth-child(2) {
transition-delay: 130ms;
}
.mm-17__card:nth-child(3) {
transition-delay: 220ms;
}
.mm-17__card:nth-child(4) {
transition-delay: 310ms;
}
.mm-17__card:nth-child(n+5) {
transition-delay: 310ms;
}
.mm-17__links li {
opacity: 0;
transition: opacity 520ms ease;
}
.mm-17__item:hover > .mm-17__panel .mm-17__links li,
.mm-17__item:focus-within > .mm-17__panel .mm-17__links li,
.mm-17__item--open > .mm-17__panel .mm-17__links li {
opacity: 1;
}
.mm-17__links li:nth-child(1) {
transition-delay: 120ms;
}
.mm-17__links li:nth-child(2) {
transition-delay: 190ms;
}
.mm-17__links li:nth-child(3) {
transition-delay: 260ms;
}
.mm-17__links li:nth-child(4) {
transition-delay: 330ms;
}
.mm-17__links li:nth-child(5) {
transition-delay: 400ms;
}
.mm-17__links li:nth-child(6) {
transition-delay: 470ms;
}
.mm-17__links li:nth-child(n+7) {
transition-delay: 470ms;
}
@media (prefers-reduced-motion: reduce) {
.mm-17__card,
.mm-17__links li {
transition-duration: 1ms;
transition-delay: 0s;
animation: none;
}
.mm-17__card {
opacity: 1;
translate: none;
scale: 1;
}
.mm-17__links li {
opacity: 1;
}
}.mm-17 {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
--mm-17-card-min: 11rem;
--mm-17-zoom: 1.06;
--mm-17-font-display: "Avenir Next", "Segoe UI", ui-rounded, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background-color: var(--mm-17-bg);
background-image: radial-gradient(64rem 32rem at 88% -8%, color-mix(in oklab, var(--mm-17-acc) 14%, transparent), transparent 62%);
color: var(--mm-17-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.mm-17 *,
.mm-17 *::before,
.mm-17 *::after {
box-sizing: border-box;
}
.mm-17 :focus-visible {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
border-radius: 12px;
}
.mm-17__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
@media (prefers-color-scheme: dark) {
.mm-17 {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
}
}
@media (prefers-color-scheme: light) {
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
}
.mm-17__theme {
position: absolute;
inset-block-start: 13px;
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 60;
}
.mm-17__theme-input {
position: absolute;
opacity: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
cursor: pointer;
}
.mm-17__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mm-17-line);
border-radius: 999px;
background: var(--mm-17-surf);
color: var(--mm-17-ink);
cursor: pointer;
box-shadow: 0 10px 26px -16px rgba(35, 38, 31, 0.7);
}
.mm-17__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mm-17__theme-input:focus-visible + .mm-17__theme-label {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
}
.mm-17__header {
position: relative;
z-index: 10;
inline-size: 100%;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
}
.mm-17__bar {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: flex;
align-items: center;
gap: clamp(0.5rem, 2vw, 2rem);
flex-wrap: wrap;
min-block-size: 70px;
padding-inline-end: 60px;
}
.mm-17__brand {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--mm-17-ink);
text-decoration: none;
font: 600 1.1rem/1 var(--mm-17-font-display);
letter-spacing: -0.01em;
}
.mm-17__brand svg {
inline-size: 22px;
block-size: 22px;
color: var(--mm-17-acc);
}
.mm-17__nav {
min-inline-size: 0;
}
.mm-17__list {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
}
.mm-17__item {
position: static;
}
.mm-17__trigger,
.mm-17__plain {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.9rem;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--mm-17-ink);
font: 600 0.92rem/1 var(--mm-17-font-display);
text-decoration: none;
cursor: pointer;
}
.mm-17__item--open > .mm-17__trigger,
.mm-17__item:hover > .mm-17__trigger,
.mm-17__item:focus-within > .mm-17__trigger {
background: color-mix(in oklab, var(--mm-17-acc) 16%, transparent);
color: var(--mm-17-acc);
}
.mm-17__plain:hover,
.mm-17__trigger:hover {
color: var(--mm-17-acc);
}
.mm-17__utils {
margin-inline-start: auto;
display: flex;
align-items: center;
gap: 0.5rem;
}
.mm-17__util {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.6rem;
color: var(--mm-17-mut);
text-decoration: none;
font: 600 0.86rem/1 var(--mm-17-font-display);
}
.mm-17__bag {
display: inline-flex;
align-items: center;
min-block-size: 40px;
padding-inline: 0.95rem;
border-radius: 999px;
border: 1px solid var(--mm-17-line);
color: var(--mm-17-ink);
text-decoration: none;
font: 600 0.84rem/1 var(--mm-17-font-display);
}
.mm-17__panel {
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
z-index: 30;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
box-shadow: 0 34px 70px -40px rgba(35, 38, 31, 0.55);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 180ms ease, visibility 0s linear 180ms;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
@media (hover: hover) {
.mm-17__item:hover > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
}
.mm-17__panel-inner {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1rem, 3vw, 2rem);
padding-block: clamp(1.1rem, 3vw, 2rem);
}
@media (min-width: 68rem) {
.mm-17__panel-inner {
grid-template-columns: minmax(0, 1fr) 17rem;
}
}
.mm-17__cards {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--mm-17-card-min), 1fr));
gap: clamp(0.6rem, 2vw, 1rem);
min-inline-size: 0;
}
.mm-17__card {
display: grid;
gap: 0.2rem;
text-decoration: none;
color: inherit;
min-inline-size: 0;
}
.mm-17__crop {
display: block;
overflow: hidden;
border-radius: 14px;
background: var(--mm-17-line);
aspect-ratio: 4 / 3;
}
.mm-17__crop img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
display: block;
transition: scale 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mm-17__card:hover .mm-17__crop img {
scale: var(--mm-17-zoom);
}
.mm-17__card-name {
margin-block-start: 0.35rem;
font: 600 0.95rem/1.3 var(--mm-17-font-display);
min-inline-size: 0;
}
.mm-17__card:hover .mm-17__card-name {
color: var(--mm-17-acc);
}
.mm-17__card-meta {
font-size: 0.76rem;
color: var(--mm-17-mut);
}
.mm-17__aside {
min-inline-size: 0;
display: grid;
align-content: start;
gap: 0.3rem;
padding: clamp(0.9rem, 2.4vw, 1.2rem);
border-radius: 16px;
background: color-mix(in oklab, var(--mm-17-acc) 9%, transparent);
}
.mm-17__aside-head {
margin: 0;
font: 700 0.68rem/1 var(--mm-17-font-display);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__links {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
}
.mm-17__links a {
display: flex;
align-items: center;
min-block-size: 44px;
color: var(--mm-17-ink);
text-decoration: none;
font-size: 0.9rem;
min-inline-size: 0;
}
.mm-17__links a:hover {
color: var(--mm-17-acc);
text-decoration: underline;
text-underline-offset: 3px;
}
.mm-17__aside-cta {
display: inline-flex;
align-items: center;
min-block-size: 44px;
margin-block-start: 0.2rem;
color: var(--mm-17-acc);
font: 700 0.86rem/1 var(--mm-17-font-display);
text-decoration: none;
}
.mm-17__page {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
padding-block: clamp(3rem, 9vw, 5.5rem);
}
.mm-17__hero {
max-inline-size: 34rem;
display: grid;
gap: 0.6rem;
}
.mm-17__kicker {
margin: 0;
font: 700 0.7rem/1 var(--mm-17-font-display);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__h1 {
margin: 0;
font: 600 clamp(2rem, 6vw, 3.4rem)/1.06 var(--mm-17-font-display);
letter-spacing: -0.025em;
text-wrap: balance;
}
.mm-17__cta {
justify-self: start;
display: inline-flex;
align-items: center;
min-block-size: 48px;
margin-block-start: 0.4rem;
padding-inline: 1.3rem;
border-radius: 999px;
background: var(--mm-17-acc);
color: var(--mm-17-on-acc);
text-decoration: none;
font: 700 0.92rem/1 var(--mm-17-font-display);
}
@media (max-width: 54rem) {
.mm-17__bar {
flex-direction: column;
align-items: stretch;
padding-block: 0.6rem;
}
.mm-17__list {
flex-direction: column;
}
.mm-17__utils {
margin-inline-start: 0;
}
.mm-17__panel {
position: static;
opacity: 1;
visibility: visible;
pointer-events: auto;
box-shadow: none;
display: none;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
display: block;
}
.mm-17__panel-inner {
inline-size: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.mm-17__panel,
.mm-17__crop img {
transition-duration: 1ms;
}
}
@media (hover: hover) and (pointer: fine) {
.mm-17__trigger {
position: relative;
}
.mm-17__item:hover > .mm-17__trigger::after,
.mm-17__item:focus-within > .mm-17__trigger::after {
content: "";
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
block-size: 16px;
}
}
/* Entry signature — slow organic settle; cards ease up over half a second, links follow on a long stagger */
.mm-17__card {
opacity: 0;
translate: 0 12px;
scale: 0.985;
transition: opacity 420ms ease, translate 580ms cubic-bezier(0.16, 1, 0.3, 1), scale 580ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-17__item:hover > .mm-17__panel .mm-17__card,
.mm-17__item:focus-within > .mm-17__panel .mm-17__card,
.mm-17__item--open > .mm-17__panel .mm-17__card {
opacity: 1;
translate: 0 0;
scale: 1;
}
.mm-17__card:nth-child(1) {
transition-delay: 40ms;
}
.mm-17__card:nth-child(2) {
transition-delay: 130ms;
}
.mm-17__card:nth-child(3) {
transition-delay: 220ms;
}
.mm-17__card:nth-child(4) {
transition-delay: 310ms;
}
.mm-17__card:nth-child(n+5) {
transition-delay: 310ms;
}
.mm-17__links li {
opacity: 0;
transition: opacity 520ms ease;
}
.mm-17__item:hover > .mm-17__panel .mm-17__links li,
.mm-17__item:focus-within > .mm-17__panel .mm-17__links li,
.mm-17__item--open > .mm-17__panel .mm-17__links li {
opacity: 1;
}
.mm-17__links li:nth-child(1) {
transition-delay: 120ms;
}
.mm-17__links li:nth-child(2) {
transition-delay: 190ms;
}
.mm-17__links li:nth-child(3) {
transition-delay: 260ms;
}
.mm-17__links li:nth-child(4) {
transition-delay: 330ms;
}
.mm-17__links li:nth-child(5) {
transition-delay: 400ms;
}
.mm-17__links li:nth-child(6) {
transition-delay: 470ms;
}
.mm-17__links li:nth-child(n+7) {
transition-delay: 470ms;
}
@media (prefers-reduced-motion: reduce) {
.mm-17__card,
.mm-17__links li {
transition-duration: 1ms;
transition-delay: 0s;
animation: none;
}
.mm-17__card {
opacity: 1;
translate: none;
scale: 1;
}
.mm-17__links li {
opacity: 1;
}
}Here's a working CSS Mega Menu 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: Mega Menu with Category Image Cards
Source: https://codefronts.com/navigation/css-mega-menus/biophilic-studio/
A category grid where each destination is an image card rather than a text link, for the case where the picture carries more information than the label. The demo is built around image loading: an unsized image inside a panel that opens on hover causes a visible reflow the first time it appears, so every card reserves its box before the file arrives.
## HTML
```html
<section class="mm-17" aria-labelledby="mm-17-title">
<h2 class="mm-17__sr" id="mm-17-title">Fern Studio</h2>
<div class="mm-17__theme">
<input class="mm-17__theme-input" type="checkbox" id="mm-17-theme">
<label class="mm-17__theme-label" for="mm-17-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="mm-17__sr">Toggle dark theme</span>
</label>
</div>
<header class="mm-17__header">
<div class="mm-17__bar">
<a class="mm-17__brand" href="#mm-17-title">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 21c0-7 4-12 9-13-1 8-4 12-9 13Z" fill="currentColor" opacity=".85"/><path d="M12 21C9 15 5 12 3 11c1 6 4 9 9 10Z" fill="currentColor" opacity=".5"/></svg>
Fern Studio
</a>
<nav class="mm-17__nav" aria-label="Primary">
<ul class="mm-17__list">
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-shop" aria-expanded="false" aria-controls="mm-17-panel-shop">Shop by room</button>
<div class="mm-17__panel" id="mm-17-panel-shop" aria-labelledby="mm-17-trigger-shop">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-living/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Living room</span>
<span class="mm-17__card-meta">34 pieces · low light</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-kitchen/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Kitchen</span>
<span class="mm-17__card-meta">21 pieces · herbs</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-studio/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Workspace</span>
<span class="mm-17__card-meta">18 pieces · desk scale</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-bath/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Bathroom</span>
<span class="mm-17__card-meta">12 pieces · humidity</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-balcony/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Balcony</span>
<span class="mm-17__card-meta">27 pieces · hardy</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-hall/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Hallway</span>
<span class="mm-17__card-meta">9 pieces · shade</span>
</a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Care and setup</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Light and watering guide</a></li>
<li><a href="#mm-17-title">Repotting service</a></li>
<li><a href="#mm-17-title">Studio consultations</a></li>
<li><a href="#mm-17-title">Plant doctor</a></li>
</ul>
<a class="mm-17__aside-cta" href="#mm-17-title">Book a home visit</a>
</div>
</div>
</div>
</li>
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-pots" aria-expanded="false" aria-controls="mm-17-panel-pots">Pots & care</button>
<div class="mm-17__panel" id="mm-17-panel-pots" aria-labelledby="mm-17-trigger-pots">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-terracotta/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Terracotta</span><span class="mm-17__card-meta">6 sizes</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stoneware/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stoneware</span><span class="mm-17__card-meta">glazed</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stands/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stands</span><span class="mm-17__card-meta">oak and steel</span></a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Essentials</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Soil and grit</a></li>
<li><a href="#mm-17-title">Feed</a></li>
<li><a href="#mm-17-title">Tools</a></li>
</ul>
</div>
</div>
</div>
</li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Journal</a></li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Studio</a></li>
</ul>
</nav>
<div class="mm-17__utils">
<a class="mm-17__util" href="#mm-17-title">Account</a>
<a class="mm-17__bag" href="#mm-17-title">Basket · 2</a>
</div>
</div>
</header>
<main class="mm-17__page">
<div class="mm-17__hero">
<p class="mm-17__kicker">Delivered in 3 days, potted and fed</p>
<h1 class="mm-17__h1">Plants chosen for the light you actually have.</h1>
<a class="mm-17__cta" href="#mm-17-title">Take the light quiz</a>
</div>
</main>
</section>
```
## CSS
```css
.mm-17 {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
--mm-17-card-min: 11rem;
--mm-17-zoom: 1.06;
--mm-17-font-display: "Avenir Next", "Segoe UI", ui-rounded, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background-color: var(--mm-17-bg);
background-image: radial-gradient(64rem 32rem at 88% -8%, color-mix(in oklab, var(--mm-17-acc) 14%, transparent), transparent 62%);
color: var(--mm-17-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.mm-17 *,
.mm-17 *::before,
.mm-17 *::after {
box-sizing: border-box;
}
.mm-17 :focus-visible {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
border-radius: 12px;
}
.mm-17__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
@media (prefers-color-scheme: dark) {
.mm-17 {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
}
}
@media (prefers-color-scheme: light) {
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
}
.mm-17__theme {
position: absolute;
inset-block-start: 13px;
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 60;
}
.mm-17__theme-input {
position: absolute;
opacity: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
cursor: pointer;
}
.mm-17__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mm-17-line);
border-radius: 999px;
background: var(--mm-17-surf);
color: var(--mm-17-ink);
cursor: pointer;
box-shadow: 0 10px 26px -16px rgba(35, 38, 31, 0.7);
}
.mm-17__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mm-17__theme-input:focus-visible + .mm-17__theme-label {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
}
.mm-17__header {
position: relative;
z-index: 10;
inline-size: 100%;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
}
.mm-17__bar {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: flex;
align-items: center;
gap: clamp(0.5rem, 2vw, 2rem);
flex-wrap: wrap;
min-block-size: 70px;
padding-inline-end: 60px;
}
.mm-17__brand {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--mm-17-ink);
text-decoration: none;
font: 600 1.1rem/1 var(--mm-17-font-display);
letter-spacing: -0.01em;
}
.mm-17__brand svg {
inline-size: 22px;
block-size: 22px;
color: var(--mm-17-acc);
}
.mm-17__nav {
min-inline-size: 0;
}
.mm-17__list {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
}
.mm-17__item {
position: static;
}
.mm-17__trigger,
.mm-17__plain {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.9rem;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--mm-17-ink);
font: 600 0.92rem/1 var(--mm-17-font-display);
text-decoration: none;
cursor: pointer;
}
.mm-17__item--open > .mm-17__trigger,
.mm-17__item:hover > .mm-17__trigger,
.mm-17__item:focus-within > .mm-17__trigger {
background: color-mix(in oklab, var(--mm-17-acc) 16%, transparent);
color: var(--mm-17-acc);
}
.mm-17__plain:hover,
.mm-17__trigger:hover {
color: var(--mm-17-acc);
}
.mm-17__utils {
margin-inline-start: auto;
display: flex;
align-items: center;
gap: 0.5rem;
}
.mm-17__util {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.6rem;
color: var(--mm-17-mut);
text-decoration: none;
font: 600 0.86rem/1 var(--mm-17-font-display);
}
.mm-17__bag {
display: inline-flex;
align-items: center;
min-block-size: 40px;
padding-inline: 0.95rem;
border-radius: 999px;
border: 1px solid var(--mm-17-line);
color: var(--mm-17-ink);
text-decoration: none;
font: 600 0.84rem/1 var(--mm-17-font-display);
}
.mm-17__panel {
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
z-index: 30;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
box-shadow: 0 34px 70px -40px rgba(35, 38, 31, 0.55);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 180ms ease, visibility 0s linear 180ms;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
@media (hover: hover) {
.mm-17__item:hover > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
}
.mm-17__panel-inner {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1rem, 3vw, 2rem);
padding-block: clamp(1.1rem, 3vw, 2rem);
}
@media (min-width: 68rem) {
.mm-17__panel-inner {
grid-template-columns: minmax(0, 1fr) 17rem;
}
}
.mm-17__cards {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--mm-17-card-min), 1fr));
gap: clamp(0.6rem, 2vw, 1rem);
min-inline-size: 0;
}
.mm-17__card {
display: grid;
gap: 0.2rem;
text-decoration: none;
color: inherit;
min-inline-size: 0;
}
.mm-17__crop {
display: block;
overflow: hidden;
border-radius: 14px;
background: var(--mm-17-line);
aspect-ratio: 4 / 3;
}
.mm-17__crop img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
display: block;
transition: scale 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mm-17__card:hover .mm-17__crop img {
scale: var(--mm-17-zoom);
}
.mm-17__card-name {
margin-block-start: 0.35rem;
font: 600 0.95rem/1.3 var(--mm-17-font-display);
min-inline-size: 0;
}
.mm-17__card:hover .mm-17__card-name {
color: var(--mm-17-acc);
}
.mm-17__card-meta {
font-size: 0.76rem;
color: var(--mm-17-mut);
}
.mm-17__aside {
min-inline-size: 0;
display: grid;
align-content: start;
gap: 0.3rem;
padding: clamp(0.9rem, 2.4vw, 1.2rem);
border-radius: 16px;
background: color-mix(in oklab, var(--mm-17-acc) 9%, transparent);
}
.mm-17__aside-head {
margin: 0;
font: 700 0.68rem/1 var(--mm-17-font-display);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__links {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
}
.mm-17__links a {
display: flex;
align-items: center;
min-block-size: 44px;
color: var(--mm-17-ink);
text-decoration: none;
font-size: 0.9rem;
min-inline-size: 0;
}
.mm-17__links a:hover {
color: var(--mm-17-acc);
text-decoration: underline;
text-underline-offset: 3px;
}
.mm-17__aside-cta {
display: inline-flex;
align-items: center;
min-block-size: 44px;
margin-block-start: 0.2rem;
color: var(--mm-17-acc);
font: 700 0.86rem/1 var(--mm-17-font-display);
text-decoration: none;
}
.mm-17__page {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
padding-block: clamp(3rem, 9vw, 5.5rem);
}
.mm-17__hero {
max-inline-size: 34rem;
display: grid;
gap: 0.6rem;
}
.mm-17__kicker {
margin: 0;
font: 700 0.7rem/1 var(--mm-17-font-display);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__h1 {
margin: 0;
font: 600 clamp(2rem, 6vw, 3.4rem)/1.06 var(--mm-17-font-display);
letter-spacing: -0.025em;
text-wrap: balance;
}
.mm-17__cta {
justify-self: start;
display: inline-flex;
align-items: center;
min-block-size: 48px;
margin-block-start: 0.4rem;
padding-inline: 1.3rem;
border-radius: 999px;
background: var(--mm-17-acc);
color: var(--mm-17-on-acc);
text-decoration: none;
font: 700 0.92rem/1 var(--mm-17-font-display);
}
@media (max-width: 54rem) {
.mm-17__bar {
flex-direction: column;
align-items: stretch;
padding-block: 0.6rem;
}
.mm-17__list {
flex-direction: column;
}
.mm-17__utils {
margin-inline-start: 0;
}
.mm-17__panel {
position: static;
opacity: 1;
visibility: visible;
pointer-events: auto;
box-shadow: none;
display: none;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
display: block;
}
.mm-17__panel-inner {
inline-size: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.mm-17__panel,
.mm-17__crop img {
transition-duration: 1ms;
}
}
@media (hover: hover) and (pointer: fine) {
.mm-17__trigger {
position: relative;
}
.mm-17__item:hover > .mm-17__trigger::after,
.mm-17__item:focus-within > .mm-17__trigger::after {
content: "";
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
block-size: 16px;
}
}
/* Entry signature — slow organic settle; cards ease up over half a second, links follow on a long stagger */
.mm-17__card {
opacity: 0;
translate: 0 12px;
scale: 0.985;
transition: opacity 420ms ease, translate 580ms cubic-bezier(0.16, 1, 0.3, 1), scale 580ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-17__item:hover > .mm-17__panel .mm-17__card,
.mm-17__item:focus-within > .mm-17__panel .mm-17__card,
.mm-17__item--open > .mm-17__panel .mm-17__card {
opacity: 1;
translate: 0 0;
scale: 1;
}
.mm-17__card:nth-child(1) {
transition-delay: 40ms;
}
.mm-17__card:nth-child(2) {
transition-delay: 130ms;
}
.mm-17__card:nth-child(3) {
transition-delay: 220ms;
}
.mm-17__card:nth-child(4) {
transition-delay: 310ms;
}
.mm-17__card:nth-child(n+5) {
transition-delay: 310ms;
}
.mm-17__links li {
opacity: 0;
transition: opacity 520ms ease;
}
.mm-17__item:hover > .mm-17__panel .mm-17__links li,
.mm-17__item:focus-within > .mm-17__panel .mm-17__links li,
.mm-17__item--open > .mm-17__panel .mm-17__links li {
opacity: 1;
}
.mm-17__links li:nth-child(1) {
transition-delay: 120ms;
}
.mm-17__links li:nth-child(2) {
transition-delay: 190ms;
}
.mm-17__links li:nth-child(3) {
transition-delay: 260ms;
}
.mm-17__links li:nth-child(4) {
transition-delay: 330ms;
}
.mm-17__links li:nth-child(5) {
transition-delay: 400ms;
}
.mm-17__links li:nth-child(6) {
transition-delay: 470ms;
}
.mm-17__links li:nth-child(n+7) {
transition-delay: 470ms;
}
@media (prefers-reduced-motion: reduce) {
.mm-17__card,
.mm-17__links li {
transition-duration: 1ms;
transition-delay: 0s;
animation: none;
}
.mm-17__card {
opacity: 1;
translate: none;
scale: 1;
}
.mm-17__links li {
opacity: 1;
}
}
```Here's a working CSS Mega Menu 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: Mega Menu with Category Image Cards
Source: https://codefronts.com/navigation/css-mega-menus/biophilic-studio/
A category grid where each destination is an image card rather than a text link, for the case where the picture carries more information than the label. The demo is built around image loading: an unsized image inside a panel that opens on hover causes a visible reflow the first time it appears, so every card reserves its box before the file arrives.
## HTML
```html
<section class="mm-17" aria-labelledby="mm-17-title">
<h2 class="mm-17__sr" id="mm-17-title">Fern Studio</h2>
<div class="mm-17__theme">
<input class="mm-17__theme-input" type="checkbox" id="mm-17-theme">
<label class="mm-17__theme-label" for="mm-17-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="mm-17__sr">Toggle dark theme</span>
</label>
</div>
<header class="mm-17__header">
<div class="mm-17__bar">
<a class="mm-17__brand" href="#mm-17-title">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 21c0-7 4-12 9-13-1 8-4 12-9 13Z" fill="currentColor" opacity=".85"/><path d="M12 21C9 15 5 12 3 11c1 6 4 9 9 10Z" fill="currentColor" opacity=".5"/></svg>
Fern Studio
</a>
<nav class="mm-17__nav" aria-label="Primary">
<ul class="mm-17__list">
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-shop" aria-expanded="false" aria-controls="mm-17-panel-shop">Shop by room</button>
<div class="mm-17__panel" id="mm-17-panel-shop" aria-labelledby="mm-17-trigger-shop">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-living/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Living room</span>
<span class="mm-17__card-meta">34 pieces · low light</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-kitchen/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Kitchen</span>
<span class="mm-17__card-meta">21 pieces · herbs</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-studio/480/360" alt="" width="480" height="360" decoding="async"></span>
<span class="mm-17__card-name">Workspace</span>
<span class="mm-17__card-meta">18 pieces · desk scale</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-bath/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Bathroom</span>
<span class="mm-17__card-meta">12 pieces · humidity</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-balcony/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Balcony</span>
<span class="mm-17__card-meta">27 pieces · hardy</span>
</a></li>
<li><a class="mm-17__card" href="#mm-17-title">
<span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-hall/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span>
<span class="mm-17__card-name">Hallway</span>
<span class="mm-17__card-meta">9 pieces · shade</span>
</a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Care and setup</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Light and watering guide</a></li>
<li><a href="#mm-17-title">Repotting service</a></li>
<li><a href="#mm-17-title">Studio consultations</a></li>
<li><a href="#mm-17-title">Plant doctor</a></li>
</ul>
<a class="mm-17__aside-cta" href="#mm-17-title">Book a home visit</a>
</div>
</div>
</div>
</li>
<li class="mm-17__item">
<button class="mm-17__trigger" type="button" id="mm-17-trigger-pots" aria-expanded="false" aria-controls="mm-17-panel-pots">Pots & care</button>
<div class="mm-17__panel" id="mm-17-panel-pots" aria-labelledby="mm-17-trigger-pots">
<div class="mm-17__panel-inner">
<ul class="mm-17__cards">
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-terracotta/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Terracotta</span><span class="mm-17__card-meta">6 sizes</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stoneware/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stoneware</span><span class="mm-17__card-meta">glazed</span></a></li>
<li><a class="mm-17__card" href="#mm-17-title"><span class="mm-17__crop"><img src="https://picsum.photos/seed/fern-stands/480/360" alt="" width="480" height="360" loading="lazy" decoding="async"></span><span class="mm-17__card-name">Stands</span><span class="mm-17__card-meta">oak and steel</span></a></li>
</ul>
<div class="mm-17__aside">
<p class="mm-17__aside-head">Essentials</p>
<ul class="mm-17__links">
<li><a href="#mm-17-title">Soil and grit</a></li>
<li><a href="#mm-17-title">Feed</a></li>
<li><a href="#mm-17-title">Tools</a></li>
</ul>
</div>
</div>
</div>
</li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Journal</a></li>
<li class="mm-17__item"><a class="mm-17__plain" href="#mm-17-title">Studio</a></li>
</ul>
</nav>
<div class="mm-17__utils">
<a class="mm-17__util" href="#mm-17-title">Account</a>
<a class="mm-17__bag" href="#mm-17-title">Basket · 2</a>
</div>
</div>
</header>
<main class="mm-17__page">
<div class="mm-17__hero">
<p class="mm-17__kicker">Delivered in 3 days, potted and fed</p>
<h1 class="mm-17__h1">Plants chosen for the light you actually have.</h1>
<a class="mm-17__cta" href="#mm-17-title">Take the light quiz</a>
</div>
</main>
</section>
```
## CSS
```css
.mm-17 {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
--mm-17-card-min: 11rem;
--mm-17-zoom: 1.06;
--mm-17-font-display: "Avenir Next", "Segoe UI", ui-rounded, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background-color: var(--mm-17-bg);
background-image: radial-gradient(64rem 32rem at 88% -8%, color-mix(in oklab, var(--mm-17-acc) 14%, transparent), transparent 62%);
color: var(--mm-17-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.mm-17 *,
.mm-17 *::before,
.mm-17 *::after {
box-sizing: border-box;
}
.mm-17 :focus-visible {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
border-radius: 12px;
}
.mm-17__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
@media (prefers-color-scheme: dark) {
.mm-17 {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #f3f1ea;
--mm-17-surf: #fbfaf6;
--mm-17-line: #ddd8c9;
--mm-17-ink: #23261f;
--mm-17-mut: #666b5c;
--mm-17-acc: #4a6b46;
--mm-17-acc: oklch(47% 0.08 145);
--mm-17-on-acc: #ffffff;
}
}
@media (prefers-color-scheme: light) {
.mm-17:has(.mm-17__theme-input:checked) {
--mm-17-bg: #0e100d;
--mm-17-surf: #171a14;
--mm-17-line: #2a2f25;
--mm-17-ink: #eef1e8;
--mm-17-mut: #9ba393;
--mm-17-acc: #8fce87;
--mm-17-acc: oklch(80% 0.12 145);
--mm-17-on-acc: #12100e;
}
}
.mm-17__theme {
position: absolute;
inset-block-start: 13px;
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 60;
}
.mm-17__theme-input {
position: absolute;
opacity: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
cursor: pointer;
}
.mm-17__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mm-17-line);
border-radius: 999px;
background: var(--mm-17-surf);
color: var(--mm-17-ink);
cursor: pointer;
box-shadow: 0 10px 26px -16px rgba(35, 38, 31, 0.7);
}
.mm-17__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mm-17__theme-input:focus-visible + .mm-17__theme-label {
outline: 2px solid var(--mm-17-acc);
outline-offset: 3px;
}
.mm-17__header {
position: relative;
z-index: 10;
inline-size: 100%;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
}
.mm-17__bar {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: flex;
align-items: center;
gap: clamp(0.5rem, 2vw, 2rem);
flex-wrap: wrap;
min-block-size: 70px;
padding-inline-end: 60px;
}
.mm-17__brand {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--mm-17-ink);
text-decoration: none;
font: 600 1.1rem/1 var(--mm-17-font-display);
letter-spacing: -0.01em;
}
.mm-17__brand svg {
inline-size: 22px;
block-size: 22px;
color: var(--mm-17-acc);
}
.mm-17__nav {
min-inline-size: 0;
}
.mm-17__list {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
}
.mm-17__item {
position: static;
}
.mm-17__trigger,
.mm-17__plain {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.9rem;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--mm-17-ink);
font: 600 0.92rem/1 var(--mm-17-font-display);
text-decoration: none;
cursor: pointer;
}
.mm-17__item--open > .mm-17__trigger,
.mm-17__item:hover > .mm-17__trigger,
.mm-17__item:focus-within > .mm-17__trigger {
background: color-mix(in oklab, var(--mm-17-acc) 16%, transparent);
color: var(--mm-17-acc);
}
.mm-17__plain:hover,
.mm-17__trigger:hover {
color: var(--mm-17-acc);
}
.mm-17__utils {
margin-inline-start: auto;
display: flex;
align-items: center;
gap: 0.5rem;
}
.mm-17__util {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.6rem;
color: var(--mm-17-mut);
text-decoration: none;
font: 600 0.86rem/1 var(--mm-17-font-display);
}
.mm-17__bag {
display: inline-flex;
align-items: center;
min-block-size: 40px;
padding-inline: 0.95rem;
border-radius: 999px;
border: 1px solid var(--mm-17-line);
color: var(--mm-17-ink);
text-decoration: none;
font: 600 0.84rem/1 var(--mm-17-font-display);
}
.mm-17__panel {
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
z-index: 30;
background: var(--mm-17-surf);
border-block-end: 1px solid var(--mm-17-line);
box-shadow: 0 34px 70px -40px rgba(35, 38, 31, 0.55);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 180ms ease, visibility 0s linear 180ms;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
@media (hover: hover) {
.mm-17__item:hover > .mm-17__panel {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
}
.mm-17__panel-inner {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1rem, 3vw, 2rem);
padding-block: clamp(1.1rem, 3vw, 2rem);
}
@media (min-width: 68rem) {
.mm-17__panel-inner {
grid-template-columns: minmax(0, 1fr) 17rem;
}
}
.mm-17__cards {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--mm-17-card-min), 1fr));
gap: clamp(0.6rem, 2vw, 1rem);
min-inline-size: 0;
}
.mm-17__card {
display: grid;
gap: 0.2rem;
text-decoration: none;
color: inherit;
min-inline-size: 0;
}
.mm-17__crop {
display: block;
overflow: hidden;
border-radius: 14px;
background: var(--mm-17-line);
aspect-ratio: 4 / 3;
}
.mm-17__crop img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
display: block;
transition: scale 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mm-17__card:hover .mm-17__crop img {
scale: var(--mm-17-zoom);
}
.mm-17__card-name {
margin-block-start: 0.35rem;
font: 600 0.95rem/1.3 var(--mm-17-font-display);
min-inline-size: 0;
}
.mm-17__card:hover .mm-17__card-name {
color: var(--mm-17-acc);
}
.mm-17__card-meta {
font-size: 0.76rem;
color: var(--mm-17-mut);
}
.mm-17__aside {
min-inline-size: 0;
display: grid;
align-content: start;
gap: 0.3rem;
padding: clamp(0.9rem, 2.4vw, 1.2rem);
border-radius: 16px;
background: color-mix(in oklab, var(--mm-17-acc) 9%, transparent);
}
.mm-17__aside-head {
margin: 0;
font: 700 0.68rem/1 var(--mm-17-font-display);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__links {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
}
.mm-17__links a {
display: flex;
align-items: center;
min-block-size: 44px;
color: var(--mm-17-ink);
text-decoration: none;
font-size: 0.9rem;
min-inline-size: 0;
}
.mm-17__links a:hover {
color: var(--mm-17-acc);
text-decoration: underline;
text-underline-offset: 3px;
}
.mm-17__aside-cta {
display: inline-flex;
align-items: center;
min-block-size: 44px;
margin-block-start: 0.2rem;
color: var(--mm-17-acc);
font: 700 0.86rem/1 var(--mm-17-font-display);
text-decoration: none;
}
.mm-17__page {
inline-size: min(80rem, calc(100% - 2rem));
margin-inline: auto;
padding-block: clamp(3rem, 9vw, 5.5rem);
}
.mm-17__hero {
max-inline-size: 34rem;
display: grid;
gap: 0.6rem;
}
.mm-17__kicker {
margin: 0;
font: 700 0.7rem/1 var(--mm-17-font-display);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--mm-17-acc);
}
.mm-17__h1 {
margin: 0;
font: 600 clamp(2rem, 6vw, 3.4rem)/1.06 var(--mm-17-font-display);
letter-spacing: -0.025em;
text-wrap: balance;
}
.mm-17__cta {
justify-self: start;
display: inline-flex;
align-items: center;
min-block-size: 48px;
margin-block-start: 0.4rem;
padding-inline: 1.3rem;
border-radius: 999px;
background: var(--mm-17-acc);
color: var(--mm-17-on-acc);
text-decoration: none;
font: 700 0.92rem/1 var(--mm-17-font-display);
}
@media (max-width: 54rem) {
.mm-17__bar {
flex-direction: column;
align-items: stretch;
padding-block: 0.6rem;
}
.mm-17__list {
flex-direction: column;
}
.mm-17__utils {
margin-inline-start: 0;
}
.mm-17__panel {
position: static;
opacity: 1;
visibility: visible;
pointer-events: auto;
box-shadow: none;
display: none;
}
.mm-17__item--open > .mm-17__panel,
.mm-17__item:focus-within > .mm-17__panel {
display: block;
}
.mm-17__panel-inner {
inline-size: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.mm-17__panel,
.mm-17__crop img {
transition-duration: 1ms;
}
}
@media (hover: hover) and (pointer: fine) {
.mm-17__trigger {
position: relative;
}
.mm-17__item:hover > .mm-17__trigger::after,
.mm-17__item:focus-within > .mm-17__trigger::after {
content: "";
position: absolute;
inset-inline: 0;
inset-block-start: 100%;
block-size: 16px;
}
}
/* Entry signature — slow organic settle; cards ease up over half a second, links follow on a long stagger */
.mm-17__card {
opacity: 0;
translate: 0 12px;
scale: 0.985;
transition: opacity 420ms ease, translate 580ms cubic-bezier(0.16, 1, 0.3, 1), scale 580ms cubic-bezier(0.16, 1, 0.3, 1);
}
.mm-17__item:hover > .mm-17__panel .mm-17__card,
.mm-17__item:focus-within > .mm-17__panel .mm-17__card,
.mm-17__item--open > .mm-17__panel .mm-17__card {
opacity: 1;
translate: 0 0;
scale: 1;
}
.mm-17__card:nth-child(1) {
transition-delay: 40ms;
}
.mm-17__card:nth-child(2) {
transition-delay: 130ms;
}
.mm-17__card:nth-child(3) {
transition-delay: 220ms;
}
.mm-17__card:nth-child(4) {
transition-delay: 310ms;
}
.mm-17__card:nth-child(n+5) {
transition-delay: 310ms;
}
.mm-17__links li {
opacity: 0;
transition: opacity 520ms ease;
}
.mm-17__item:hover > .mm-17__panel .mm-17__links li,
.mm-17__item:focus-within > .mm-17__panel .mm-17__links li,
.mm-17__item--open > .mm-17__panel .mm-17__links li {
opacity: 1;
}
.mm-17__links li:nth-child(1) {
transition-delay: 120ms;
}
.mm-17__links li:nth-child(2) {
transition-delay: 190ms;
}
.mm-17__links li:nth-child(3) {
transition-delay: 260ms;
}
.mm-17__links li:nth-child(4) {
transition-delay: 330ms;
}
.mm-17__links li:nth-child(5) {
transition-delay: 400ms;
}
.mm-17__links li:nth-child(6) {
transition-delay: 470ms;
}
.mm-17__links li:nth-child(n+7) {
transition-delay: 470ms;
}
@media (prefers-reduced-motion: reduce) {
.mm-17__card,
.mm-17__links li {
transition-duration: 1ms;
transition-delay: 0s;
animation: none;
}
.mm-17__card {
opacity: 1;
translate: none;
scale: 1;
}
.mm-17__links li {
opacity: 1;
}
}
```How this works
Image cards fail in a specific, repeatable way. The panel is hidden, so its images are not fetched. The pointer arrives, the panel opens, the images start loading, and until each one arrives its box is zero-height — so the grid is short and then grows as the files land. The user watches the panel jump under their cursor. Every one of those steps is correct browser behaviour; the mistake is not telling the browser how much space to keep.
Two attributes and one property fix it. width and height on the <img> give the intrinsic ratio, aspect-ratio in CSS enforces the ratio you actually want, and object-fit: cover handles the mismatch between the two. The box is now reserved at parse time and the grid is stable from the first paint, whether the image is cached, slow, or missing entirely.
Make the whole card the link. An image link plus a caption link is two tab stops to one destination, two announcements, and two hover targets that look like one. Wrap the whole card in a single <a>, give the image alt="" because the caption already names the destination, and the card becomes one control with one accessible name.
Be honest about the cost. Twelve category images is twelve requests behind a hover, and on a slow connection that is a worse experience than twelve text links, not a better one. Use image cards when the image genuinely distinguishes the categories — plants, rooms, garments — and text when it does not. A grid of near-identical stock photos over generic labels is decoration charged to the user's data plan.
Make it yours
- Change the card
aspect-ratiofrom4 / 3to1 / 1or3 / 4; the grid geometry adapts with no other edit. - Retune
--mm-17-card-minto change how many cards fit per row before wrapping. - Replace the placeholder image URLs with your own CDN, keeping the width and height attributes in the same ratio.
- Set
--mm-17-zoomto1to remove the hover zoom entirely. - Add a price or stock line inside the card; the reserved image box means the panel height changes predictably.
- Recolour with the two
--mm-17-accdeclarations, hex first then oklch.
Gotchas — read before shipping
- An unsized image inside a hover-opened panel reflows the whole grid on first open, because nothing reserved its box before the file arrived.
loading="lazy"on every card means none of them start loading until the panel is displayed, so the first open is always the slow one. Load the first row eagerly.- Two links per card — one on the image, one on the label — double the tab stops and announce the same destination twice.
- A decorative image with meaningful
alttext duplicates the caption for screen reader users. When the caption names the destination, the image is decorative. - Hover zoom implemented by animating
widthorbackground-sizeforces layout or repaint per frame. Animatescaleon the image inside a clipped card.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
oklch() and the :has() theme toggle set the floor; aspect-ratio needs Chrome 88, Safari 15 and Firefox 89 and object-fit is older still, so the image handling sits below the floor. Below the oklch() line the sRGB hex declared first applies, and the reserved image boxes behave identically.