22 CSS Dropdown Menu Designs 04 / 22
Elastic Bounce Dropdown
A playful dropdown that springs open with an elastic overshoot using a carefully tuned cubic-bezier on scaleY, giving it a bouncy physical feel.
The code
<div class="dd-04">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<nav class="dd-04__nav">
<span class="dd-04__brand">◆ Bounce</span>
<div class="dd-04__items">
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Explore ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">🌟 Featured</a>
<a href="#" class="dd-04__link">🔥 Trending</a>
<a href="#" class="dd-04__link">🚀 New Arrivals</a>
<a href="#" class="dd-04__link">🏆 Top Rated</a>
<div class="dd-04__divider"></div>
<a href="#" class="dd-04__link dd-04__link--cta">View All →</a>
</div>
</div>
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Learn ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">📚 Tutorials</a>
<a href="#" class="dd-04__link">🎬 Video Courses</a>
<a href="#" class="dd-04__link">🎉 Workshops</a>
</div>
</div>
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Connect ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">💬 Community</a>
<a href="#" class="dd-04__link">👥 Forum</a>
<a href="#" class="dd-04__link">📬 Newsletter</a>
</div>
</div>
</div>
</nav>
</div> <div class="dd-04">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<nav class="dd-04__nav">
<span class="dd-04__brand">◆ Bounce</span>
<div class="dd-04__items">
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Explore ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">🌟 Featured</a>
<a href="#" class="dd-04__link">🔥 Trending</a>
<a href="#" class="dd-04__link">🚀 New Arrivals</a>
<a href="#" class="dd-04__link">🏆 Top Rated</a>
<div class="dd-04__divider"></div>
<a href="#" class="dd-04__link dd-04__link--cta">View All →</a>
</div>
</div>
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Learn ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">📚 Tutorials</a>
<a href="#" class="dd-04__link">🎬 Video Courses</a>
<a href="#" class="dd-04__link">🎉 Workshops</a>
</div>
</div>
<div class="dd-04__item">
<a href="#" class="dd-04__trigger">Connect ▾</a>
<div class="dd-04__panel">
<a href="#" class="dd-04__link">💬 Community</a>
<a href="#" class="dd-04__link">👥 Forum</a>
<a href="#" class="dd-04__link">📬 Newsletter</a>
</div>
</div>
</div>
</nav>
</div>.dd-04, .dd-04 *, .dd-04 *::before, .dd-04 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.dd-04 ::selection { background: #ec4899; color: #fff; }
.dd-04 {
--brand: #ec4899;
--brand-light: #fdf2f8;
--surface: #fff;
--text: #1e1b4b;
--muted: #6b7280;
--border: #f3f4f6;
font-family: 'Plus Jakarta Sans', sans-serif;
min-height: 360px;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 32px 20px;
background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #ede9fe 100%);
}
.dd-04__nav {
display: flex;
align-items: center;
gap: 8px;
background: var(--surface);
border-radius: 100px;
padding: 8px 8px 8px 24px;
box-shadow: 0 4px 24px rgba(236,72,153,.15), 0 1px 4px rgba(0,0,0,.05);
position: relative;
z-index: 100;
}
.dd-04__brand {
font-size: 16px;
font-weight: 700;
color: var(--brand);
margin-right: 8px;
letter-spacing: -0.3px;
}
.dd-04__items { display: flex; gap: 2px; }
.dd-04__item { position: relative; }
.dd-04__item::after {
content: "";
position: absolute;
left: 0; right: 0;
top: 100%;
height: 10px;
/* hover-bridge: invisible strip below the trigger covering
the visible gap before the panel. Lives on .__item (not
the panel, which has overflow:hidden / pointer-events:
none in its closed state) so the parent :hover stays
active while the cursor traverses the gap. */
}
.dd-04__trigger {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: 100px;
color: var(--text);
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: background 0.15s, color 0.15s;
}
.dd-04__trigger:hover, .dd-04__item:hover .dd-04__trigger {
background: var(--brand-light);
color: var(--brand);
}
/* elastic bounce panel */
.dd-04__panel {
position: absolute;
top: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) scaleY(0);
transform-origin: top center;
min-width: 190px;
background: var(--surface);
border: 1px solid rgba(236,72,153,.12);
border-radius: 16px;
box-shadow: 0 12px 40px rgba(236,72,153,.18), 0 2px 8px rgba(0,0,0,.05);
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
pointer-events: none;
opacity: 0;
transition:
transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.18s ease;
}
.dd-04__item:hover .dd-04__panel {
transform: translateX(-50%) scaleY(1);
opacity: 1;
pointer-events: auto;
}
.dd-04__link {
display: block;
padding: 10px 14px;
border-radius: 10px;
text-decoration: none;
color: var(--text);
font-size: 13.5px;
font-weight: 500;
transition: background 0.15s, color 0.15s;
}
.dd-04__link:hover { background: var(--brand-light); color: var(--brand); }
.dd-04__link--cta {
color: var(--brand);
font-weight: 700;
}
.dd-04__divider {
height: 1px;
background: var(--border);
margin: 4px 0;
}
@media (prefers-reduced-motion: reduce) {
.dd-04__panel { transition: none; }
} .dd-04, .dd-04 *, .dd-04 *::before, .dd-04 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.dd-04 ::selection { background: #ec4899; color: #fff; }
.dd-04 {
--brand: #ec4899;
--brand-light: #fdf2f8;
--surface: #fff;
--text: #1e1b4b;
--muted: #6b7280;
--border: #f3f4f6;
font-family: 'Plus Jakarta Sans', sans-serif;
min-height: 360px;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 32px 20px;
background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #ede9fe 100%);
}
.dd-04__nav {
display: flex;
align-items: center;
gap: 8px;
background: var(--surface);
border-radius: 100px;
padding: 8px 8px 8px 24px;
box-shadow: 0 4px 24px rgba(236,72,153,.15), 0 1px 4px rgba(0,0,0,.05);
position: relative;
z-index: 100;
}
.dd-04__brand {
font-size: 16px;
font-weight: 700;
color: var(--brand);
margin-right: 8px;
letter-spacing: -0.3px;
}
.dd-04__items { display: flex; gap: 2px; }
.dd-04__item { position: relative; }
.dd-04__item::after {
content: "";
position: absolute;
left: 0; right: 0;
top: 100%;
height: 10px;
/* hover-bridge: invisible strip below the trigger covering
the visible gap before the panel. Lives on .__item (not
the panel, which has overflow:hidden / pointer-events:
none in its closed state) so the parent :hover stays
active while the cursor traverses the gap. */
}
.dd-04__trigger {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: 100px;
color: var(--text);
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: background 0.15s, color 0.15s;
}
.dd-04__trigger:hover, .dd-04__item:hover .dd-04__trigger {
background: var(--brand-light);
color: var(--brand);
}
/* elastic bounce panel */
.dd-04__panel {
position: absolute;
top: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) scaleY(0);
transform-origin: top center;
min-width: 190px;
background: var(--surface);
border: 1px solid rgba(236,72,153,.12);
border-radius: 16px;
box-shadow: 0 12px 40px rgba(236,72,153,.18), 0 2px 8px rgba(0,0,0,.05);
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
pointer-events: none;
opacity: 0;
transition:
transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.18s ease;
}
.dd-04__item:hover .dd-04__panel {
transform: translateX(-50%) scaleY(1);
opacity: 1;
pointer-events: auto;
}
.dd-04__link {
display: block;
padding: 10px 14px;
border-radius: 10px;
text-decoration: none;
color: var(--text);
font-size: 13.5px;
font-weight: 500;
transition: background 0.15s, color 0.15s;
}
.dd-04__link:hover { background: var(--brand-light); color: var(--brand); }
.dd-04__link--cta {
color: var(--brand);
font-weight: 700;
}
.dd-04__divider {
height: 1px;
background: var(--border);
margin: 4px 0;
}
@media (prefers-reduced-motion: reduce) {
.dd-04__panel { transition: none; }
}How this works
The panel uses transform: scaleY(0); transform-origin: top center to collapse to zero height without affecting document layout (unlike max-height). On hover, it transitions to scaleY(1) with the easing function cubic-bezier(0.34, 1.56, 0.64, 1) — the second Y value of 1.56 pushes the scale beyond 1.0 during the tween, creating the elastic overshoot that snaps back to exactly 1.
Because scaleY squishes the children too, all direct child content gets a counteracting transform: scaleY(calc(1 / var(--scale))) trick, or more practically, the content wrapper gets its own inverse transition. Here the children use opacity timing offset to appear only after the panel is mostly expanded, masking the squish.
Customize
- Tune the bounciness by adjusting the cubic-bezier — try
cubic-bezier(0.68, -0.55, 0.27, 1.55)for a more aggressive back-swing. - Change
transform-origintotop leftfor a corner-anchored spring that fans out from the trigger. - Apply the same easing to the close transition by using a different
cubic-bezieron the default state — e.g.transition: transform 0.2s ease-infor a fast snap-shut. - Add a subtle
box-shadowtransition from0 0 0 rgba(0,0,0,0)to full shadow so the elevation animates in with the panel.
Watch out for
scaleY(0)collapses the element visually but keeps it in the DOM flow — usepointer-events: noneto prevent invisible items from intercepting clicks.- The overshoot makes the panel briefly taller than its final size — ensure the parent container has enough clearance or
overflow: visible. - Text inside a
scaleY-transformed container appears squashed during animation — stagger the content opacity to delay its appearance until the scale settles.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 36+ | 9+ | 16+ | 36+ |
cubic-bezier easing and scaleY transforms are fully supported across all modern browsers.