Jess Benson Jess Benson - 4 months ago 15
CSS Question

Pure css multi-tiered menu with functioning breadcrumb navigation

I am trying to create a multi-tiered menu with a breadcrumb navigation, without using javascript. I have come across loads of pure css menus and breadcrumbs, but never combined and working together. Here’s a design of what I’m trying to achieve (click on the ‘more’ hamburger menu):

https://invis.io/857RUKE6M

And this is what I have so far in my html/css (see codepen link below). Please forgive the crude/hacky code. At this point I am simply testing ideas, I will simplify and beautify my code once I’ve found a solution.

http://codepen.io/jessbenz/pen/LZWjjz

Here's a code snippet, but please look at the codepen link above to get a better feel:

<div class="smart-nav">
<input type="radio" id="bread-home" class="breadcrumb" name="bread" />
<input type="radio" id="bread-women" class="breadcrumb" name="bread" />
<input type="radio" id="bread-womens-clothing" class="breadcrumb" name="bread" />
<div class="smart-nav-panels">
<ul id="home">

<li>
<input type="radio" name="first">
<label>1 Women</label>
<ul id="women">
<li>
<input type="radio" name="second">
<label>1.1 Women's Clothing</label>
<ul id="womens-clothing">
<li>
<label>1.1.1 Women's Shirts</label>
</li>
</ul>
</li>
</ul>
</li>

<li>
<input type="radio" name="first">
<label>2 Men</label>
<ul id="men">
<li>2.1 Men's Shirts</li>
</ul>
</li>

</ul>
</div>
</div>


and my sass:

.breadcrumb:checked ~ .smart-nav-panels ul {
display: none;
}
#bread-home:checked ~ .smart-nav-panels > ul {
display: block;
}
#bread-women:checked ~ .smart-nav-panels {
#home, #women {
display: block;
}
}
#bread-womens-clothing:checked ~ .smart-nav-panels {
#home, #women, #womens-clothing {
display: block;
}
}
#bread-home:checked ~ .smart-nav-panels li input:checked > ul:first-child {
display: block;
}
.smart-nav-panels {
margin: 0;
padding: 0;
ul {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: lightgrey;
}
ul, li {
list-style: none;
margin: 0;
padding: 0;
}
> ul:first-child {
ul {
left: 100%;
}
}
li {
input + label + ul {
display: none;
}
input:checked + label + ul {
display: block;
// left:0;
}
}
}
input:checked ul {
display: block;
}


If you click through the women's clothing in my codepen sample, you’ll see I am half way there with achieving what I need. The top horizontal radio buttons represent the breadcrumbs and vertical radio buttons within the gray block represent the tier menu. The problem comes in when I select a breadcrumb radio. The correct slide is displayed but then if I select a radio within the menu again, it isn’t displaying because my breadcrumb css is taking preference and hiding the relevant content. I guess herein lies the issue with not using javascript. How do I make both my navigations aware of each other with pure css? It could be that this approach of combining two radio navigations is the incorrect one. I really hope someone can share their wisdom. :)

Thanks in advance

Answer

You don't shy away from a challenge, do you? :)

Before I launch into any more detail, I would say that the short answer is "build a static site". In other words, assuming one of your design constraints is "no javascript", move the problem to a place where you do have the luxury of using decision logic / code to make it easier to solve (ie: the server).

Even if you manage to solve this problem (and I'm not sure it's possible given the constraints of HTML/CSS), the next problem you're going to have is attaching any sort of behaviour to it all. You're going to want to load specific content based on the menu selection, and the only way you're going to do that is with:

  • a javascript event, or
  • a static link (anchor element, hence the 'why' behind my short answer)

One could load all of the content and perhaps find a way to display it conditionally, but then the question is "how deep does the rabbit hole go?". Plus if you're building for feature phones and/or slow connections, loading all of the content is going to have a negative impact on the user experience.

Having said all of that, I managed to simplify the CSS slightly and fix a bug with the display of subcategories (see comments inline). Note that only the 'Women' category behaves as expected as there are styles missing for 'Men' & 'Kids'.

.container {
  position: relative;
  width: 360px;
  height: 480px;
  border: 1px solid black;
  margin: 20px auto 0 auto;
}

.breadcrumb {
  margin-top: -20px;
  display: inline-block;
  vertical-align: top;
}

/*
.breadcrumb:checked ~ .smart-nav-panels ul {
  display: none;
}

#bread-home:checked ~ .smart-nav-panels > ul {
  display: block;
}
*/

/* hide all uls except the 'home' ul by default, replaces both of the above */
.smart-nav-panels ul ul {      
  display: none;
}

/*
#bread-women:checked ~ .smart-nav-panels {
  #home, #women {
    display: block;
  }
}

#bread-womens-clothing:checked ~ .smart-nav-panels {
  #home, #women, #womens-clothing {
    display: block;
  }
}
*/

/* these next 3 style definitions are very similar to what you had before (commented 
above), except that there is no longer a need to unhide the 'home' ul, and we're
being more explicit about which uls to hide in correspondence with the state of the
breadcrumb nav */
#bread-home:checked ~ .smart-nav-panels {
  #women {
    display: none;
  }
}    

#bread-women:checked ~ .smart-nav-panels {
  #women {
    display: block;
  }
  #womens-clothing, #womens-shoes {
    display: none;
  }
}

#bread-womens-clothing:checked ~ .smart-nav-panels {
  #women, #womens-clothing {
    display: block;
  }
}

/*
#bread-home:checked ~ .smart-nav-panels li input:checked > ul:first-child {
  display: block;
}
*/

/* (i) the above didn't work because the ul isn't a direct descendant of the input,
rather it is a sibling, and in addition it doesn't matter which breadcrumb item is
checked now */
.smart-nav-panels li input:checked ~ ul {
  display: block;
}

.smart-nav-panels {
  margin: 0;
  padding: 0;
  ul {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    background: lightgrey;
  }
  ul, li {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  > ul:first-child {
    ul {
      left: 100%;
    }
  }
  /* removed unnecessary styles here */
}
/* removed unnecessary style here */

This has solved some of the problems, but there are still many more. Solving some of them will, I suspect, create new ones. One immediate one I can think of is that you'll want to tie the state of the tiered menu to the breadcrumb in such a way that you only see as much of the breadcrumb as you're supposed to (right now you always see all of it).

At some point you're going to want events (for behaviour) and components will need to know about each other's state. While CSS has some state capabilities it provides nothing on the event front. These limitations, the cascading nature (discussed in depth in other questions, eg: lack of ancestor selector) and coupling to the HTML structure all contribute to make this a very hard problem to solve with HTML & CSS alone.

I understand the desire to have this type of navigation without JS and certainly this is an interesting problem to try and solve, but ultimately I think it's the wrong way to go about it. There is a reason why javascript is so ubiquitous - our experience of the web as it is today simply wouldn't be the same without it.

(Thanks to Jess and other colleagues for the discussion that informed parts of this answer. I paraphrased liberally. Hopefully this is of benefit to someone else.)