Reputation: 1084
I'm building a mega menu component using vanilla HTML, CSS & JS. The mega menu has 3 levels of content, with the first being in the direction of row
along the banner.
I'm struggling however to use Flex how I normally would whilst maintaining the correct semantic layout. Level 2 should be a column to the left with level 3 displayed as tiles on the right. Usually I would use a grid layout to split these two sections out but I can't seem to make that happen in this case.
For example, I can apply left:0
to the level 2 ul
but I need that ul
s children to be on the right?
I've attached an image of a similar design I'd love to implement. Can anyone advise?
.container {
background-color: lightgreen;
min-height: 40px;
}
.nav-menu {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
li {
list-style: none;
}
li ul {
position: absolute;
top: 152px;
}
<div class="container">
<ul class="nav-menu">
<li>
<a href="#">Level 1</a>
<ul>
<li>
<a href="#">Level 2</a>
<ul>
<li><a href="#">Level 3</a></li>
<li><a href="#">Level 3</a></li>
<li><a href="#">Level 3</a></li>
<li><a href="#">Level 3</a></li>
<li><a href="#">Level 3</a></li>
</ul>
</li>
<li><a href="#">Level 2</a></li>
<li><a href="#">Level 2</a></li>
<li><a href="#">Level 2</a></li>
</ul>
</li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
</ul>
</div>
Upvotes: 0
Views: 265
Reputation: 5984
The trick to making this work is to ensure all your elements overlap so on hover, moving the pointer will always make sure it's inside the relevant li element. We then use position: relative
on the hovered parent and position: absolute
on the child to position the menu wherever we want. One issue with position: absolute
is that it's hard to make them expand to full screen width as the containing block is not the screen.
For level 3, I'm keeping the containing block as the level 1 element to keep it positioned just below the nav bar.
Annotated
/* pretty = no functional value, just makes it look nice */
li, ul, body {
margin:0; /*pretty*/
padding:0; /*pretty*/
}
.container {
--min-height-value: 40px;
background-color: lightgray; /*pretty*/
height:1px; /* added this to make .nav-menu height:100% work */
min-height: var(--min-height-value); /*pretty*/
}
.nav-menu {
position: relative; /* added this so we position descendants relative to the parent menu*/
display: flex;
flex-direction: row;
align-content: stretch; /* also make the child elements full height so the mouse hovers within the li at all times */
justify-content: space-between;
height: 100%; /* this height needs to match the parent so the hover remains inside the li at all times */
margin-inline:0.5rem;
}
.level2 {
--top-overhang: calc(var(--min-height-value) * 0.5); /* this positions the level 2 menu below the nav-menu */
position:absolute;
display: flex;
flex-direction:column;
top:50%;
left:0px;
padding-top:var(--top-overhang);
display:none;
background-color:#eee;
z-index:-1; /* make it appear below the nav bar, again, to keep the menu visible on hover*/
}
.nav-menu > li {
padding-inline: 1rem; /*pretty*/
}
.nav-menu > li:hover > .level2 {
display:block;
}
li:hover {
background:lightgray; /*pretty*/
}
.level2 > li {
padding-block:0.5rem; /*pretty*/
padding-inline:2rem; /*pretty*/
}
li {
list-style: none; /*pretty*/
}
a {
text-decoration:none; /*pretty*/
color:inherit; /*pretty*/
}
.nav-menu > li > a {
display:flex;
align-items:center;
height:100%;
}
.level3 {
position: absolute;
border: 1px solid lightgray; /*pretty*/
left:100%;
top:var(--top-overhang);
width:max-content;
display:none;
grid-template-columns: 1fr 1fr;
grid-auto-rows: 1fr;
gap: 0.5rem;
}
.level2 > li:hover > .level3 {
display: grid;
}
.level3 > li {
width: 10rem; /*pretty*/
aspect-ratio: 1/1; /*pretty*/
position: relative;
display:flex;
align-items: flex-end;
padding: 0.5rem; /*pretty*/
}
.level3 > li::before {
position:absolute; /*pretty*/
content:"";
inset:0.5rem; /*pretty*/
bottom: 1.5rem; /*pretty*/
border-radius: 0.5rem; /*pretty*/
background-color:gray; /*pretty*/
}
<div class="container">
<ul class="nav-menu">
<li>
<a href="#">Level 1</a>
<ul class='level2'>
<li>
<a href="#">Level 2a</a>
<ul class='level3'>
<li><a href="#">Level 3a</a></li>
<li><a href="#">Level 3b</a></li>
<li><a href="#">Level 3c</a></li>
<li><a href="#">Level 3d</a></li>
<li><a href="#">Level 3e</a></li>
</ul>
</li>
<li><a href="#">Level 2b</a></li>
<li><a href="#">Level 2c</a>
<ul class='level3'>
<li><a href="#">Level 3f</a></li>
<li><a href="#">Level 3g</a></li>
<li><a href="#">Level 3h</a></li>
<li><a href="#">Level 3i</a></li>
</ul>
</li>
<li><a href="#">Level 2d</a></li>
</ul>
</li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
<li><a href="#">Level 1</a></li>
</ul>
</div>
code below:
Upvotes: 0