<header>
<nav>
<ul class="nav">
<li>
<a href="#0">Dropdown Menu <svg class="icon" viewBox="0 0 320 512" width="100" title="caret-down">
<path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z" />
</svg></a>
<ul class="submenu">
<li><a href="#0">One</a></li>
<li><a href="#0">Two</a></li>
<li><a href="#0">Three</a></li>
</ul>
</li>
<li>
<a href="#0">Dropdown Menu <svg class="icon" viewBox="0 0 320 512" width="100">
<path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z" />
</svg></a>
<ul class="submenu">
<li><a href="#0">One</a></li>
<li><a href="#0">Two</a></li>
<li><a href="#0">Three <svg class="icon icon-rotate" viewBox="0 0 320 512" width="100">
<path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z" />
</svg></a>
<ul class="submenu submenu-2">
<li><a href="#0">One</a></li>
<li><a href="#0">Two</a></li>
</ul>
</li>
<li><a href="#0">Four</a></li>
<li><a href="#0">Five</a></li>
</ul>
</li>
</ul>
</nav>
</header>
header {
background: oklch(35% 3% 265deg);
border-bottom: 1px solid oklch(44% 3% 265deg);
}
.nav {
display: flex;
justify-content: center;
gap: 2rem;
li {
/* Submenus are absolutely positioned within */
position: relative;
/* for mouse users this works, but list items themselves aren't focusable, hence the next line */
&:hover > a,
/* if any link inside is hovered or has focus, then keep that menu open! */
&:has(a:hover, a:focus) {
/* Ya know what actually, it would be fun to set like `--dropdown-open: true` here and move the styles out to a style query instead of nesting this deeply. */
> a + .submenu {
/* block-size fallback for browsers that can't animate to auto */
block-size: auto;
block-size: calc-size(auto, size);
border: 2px solid oklch(38% 3% 265deg);
/* this isn't really needed unless the menu isn't in the DOM at all to start or you intentionally wanted to `display: none` the submenus, which is generally a bad plan. */
/*
@starting-style {
block-size: 0;
}
*/
}
}
> a {
display: flex;
gap: 0.25rem;
padding: 1rem 0.5rem;
color: #cfd8dc;
&:hover,
&:focus {
color: white;
/* May want to do nicer focus styles that don't look cut off. */
svg {
opacity: 1;
translate: 0 1px;
}
}
}
}
}
.submenu {
/* For anything at "opens and closes" `ease-in-out` is decently nice */
transition: 0.15s ease-in-out;
/* If we were animating from `display: none;` then `transition-behavior` would help make that work, but we don't need it here. Actually, for menus like this, `display: none` is bad news as it means we wouldn't be able to tab through the items properly. */
/* transition-behavior: allow-discrete; */
/* Funny how you need to replicate this in @starting-style if you need that because of `display: none` */
block-size: 0;
/* Might as well let it grow wider if it needs to */
inline-size: max-content;
min-inline-size: 100%;
position: absolute;
top: calc(100% - 5px);
left: 15px;
background: oklch(24% 3% 265deg);
border-radius: 12px;
/* You can clip directionally with `clip` which is awesome. This could be a bug though, if a sub-sub menu went lower vertically down than the sub menu. */
overflow-y: clip;
overflow-x: visible;
li {
a {
display: flex;
color: white;
padding: 0.6rem 1.2rem;
&:hover,
&:focus {
background: oklch(45% 3% 265deg);
}
}
/* gotta do this cause can't fully clip overflow */
&:first-child a {
/* 12px border radius - 2px border = 10px */
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
&:last-child a {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
}
}
.icon {
/* push right arrow to right */
margin-inline-start: auto;
fill: white;
opacity: 0.5;
inline-size: 0.7em;
transition: 0.1s ease-in-out;
&.icon-rotate {
rotate: -90deg;
}
}
/* Sub-sub menus */
.submenu-2 {
top: -5px;
left: 80%;
z-index: 1;
}
@layer foundation {
body {
font: 100% system-ui, sans-serif;
background: oklch(18% 3% 268deg);
margin: 0;
overflow: clip;
}
ul {
list-style: none;
padding: 0;
margin: 0;
a {
text-decoration: none;
}
* {
box-sizing: border-box;
}
}
// This was useful in some testing I was doing.
// setInterval(() => {
// console.log(document.activeElement);
// }, 250);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.