Search code examples
htmlcssdrop-down-menusubmenu

Clickable, toggle-able css menu with strict requirments (no js)


I am trying to come up with pure css menu with submenus (no js), out of curiosity.

Requirements are:

  • Should be able to collapse all submenues
  • Only one submenu should be visible at the same time
  • No use of hover: submenus are triggered on mouse click
  • No javascript

Here is what I have done so far:

Link to jsfiddle and code snippet below:

// no javascript
label {
  cursor: pointer;
  user-select: none;
}
label:hover {
  text-decoration: underline;
}

ul > li > ul {
  display: none;
}

#example1,
#example2,
#example3,
#example4 {
  background: #eee;
  display: inline-block;
}

#example1 > li > input,
#example2 > li > input {
  /*display: none;*/
}
#example1 > li > input:checked + ul,
#example2 > li > input:checked + ul {
  display: block;
}

#example3 > li > input[type="radio"]:checked + input[type="checkbox"]:checked + ul {
  display: block;
}

#example4:hover > li > input[type="radio"]:checked + ul {
  display: block;
}
<h3>Can collapse all, <u>BUT</u> can't display a sigle submenu at the time</h3>
<ul id="example1">
  <li>
    <label for="categories">Category</label>
    <input id="categories" type="checkbox">
    <ul>
      <li>cars</li>
      <li>animals</li>
    </ul>
  </li>
  <li>
    <label for="settings">Settings</label>
    <input id="settings" type="checkbox">
    <ul>
      <li>general</li>
      <li>notifications</li>
    </ul>
  </li>
</ul>

<h3>Can display a sigle submenu at the time, <u>BUT</u> can't collapse all</h3>
<ul id="example2">
  <li>
    <label for="categories2">Category</label>
    <input id="categories2" type="radio" name="menu">
    <ul>
      <li>cars</li>
      <li>animals</li>
    </ul>
  </li>
  <li>
    <label for="settings2">Settings</label>
    <input id="settings2" type="radio" name="menu">
    <ul>
      <li>general</li>
      <li>notifications</li>
    </ul>
  </li>
</ul>

<h3>Can display a sigle submenu at the time <u>and</u> can collapse all, <u>BUT</u> requires more then 1 click</h3>
<ul id="example3">
  <li>
    <label for="categories3check">Category</label>
    <input id="categories3radio" type="radio" name="menu">
    <input id="categories3check" type="checkbox">
    <ul>
      <li>cars</li>
      <li>animals</li>
    </ul>
  </li>
  <li>
    <label for="settings3check">Settings</label>
    <input id="settings3radio" type="radio" name="menu">
    <input id="settings3check" type="checkbox">
    <ul>
      <li>general</li>
      <li>notifications</li>
    </ul>
  </li>
</ul>

<h3>Can display a sigle submenu at the time <u>and</u> can collapse all, <u>BUT</u> uses hover and if radio is selected it opens submenu on hover</h3>
<ul id="example4">
  <li>
    <label for="categories4radio">Category</label>
    <input id="categories4radio" type="radio" name="menu">
    <ul>
      <li>cars</li>
      <li>animals</li>
    </ul>
  </li>
  <li>
    <label for="settings4radio">Settings</label>
    <input id="settings4radio" type="radio" name="menu">
    <ul>
      <li>general</li>
      <li>notifications</li>
    </ul>
  </li>
</ul>

<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

I was trying to think outside of the box but ran out of options.

Here is what it should be like:

Link to jsfiddle and code snippet below, with plenty of jQuery:

$("span").click(function() {
  var display = $(this).next()[0].style.display;
  $("ul > li > ul").hide();
  if (display == "" || display == "none")
    $(this).next().show();
  else if (display == "block")
    $(this).next().hide();
});
ul > li > ul {
  display: none;
}

span {
  user-select: none;
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
  <li>
    <span>Category</span>
    <ul>
      <li>cars</li>
      <li>animals</li>
    </ul>
  </li>
  <li>
    <span>Settings</span>
    <ul>
      <li>general</li>
      <li>notifications</li>
    </ul>
  </li>
</ul>

Is it possible to accomplish such submenu functionality without js? If yes, how. If not, why not? Any amount hacks in any order is acceptable, as long as just html/css is used and no js.


Solution

  • This answer is build on top of @kuzkuz's answer that uses :focus. Thank you very much @kuzkuz.

    The ides is to have yet another transparent "hider" span that becomes displayed on :focus and is positioned on top of menu link. Clicking "hider" span will cause to "unfocus" menu item.

    // no javascript
    label {
      cursor: pointer;
      user-select: none;
      text-decoration: underline;
    }
    
    ul>li>ul {
      display: none;
      position: relative;
      top: -20px;
      margin-top: 0;
    }
    
    #example1 {
      display: inline-block;
    }
    
    #example1>li>input {
      display: none;
    }
    
    #example1>li>label:focus~ul {
      display: block;
    }
    
    label:focus {
      outline: none;
    }
    
    label:focus + .hider {
      width: 60px;
      height: 20px;
      display: block;
      position: relative;
      top: -20px;
      cursor: pointer;
      /* background: rgba(255,0,0,0.2); */
    }
    
    .visible {
      background: rgba(255,0,0,0.2);
    <h3>Invisible hider</h3>
    <ul id="example1">
      <li>
        <label tabindex='-1' for="categories">Category</label>
        <span class="hider"></span>
        <input id="categories" type="checkbox">
        <ul>
          <li>cars</li>
          <li>animals</li>
        </ul>
      </li>
      <li>
        <label tabindex='-1' for="settings">Settings</label>
        <span class="hider"></span>
        <input id="settings" type="checkbox">
        <ul>
          <li>general</li>
          <li>notifications</li>
        </ul>
      </li>
    </ul>
    
    <h3>Visible hider for demonstation purposes</h3>
    <ul id="example1">
      <li>
        <label tabindex='-1' for="categories">Category</label>
        <span class="hider visible"></span>
        <input id="categories" type="checkbox">
        <ul>
          <li>cars</li>
          <li>animals</li>
        </ul>
      </li>
      <li>
        <label tabindex='-1' for="settings">Settings</label>
        <span class="hider visible"></span>
        <input id="settings" type="checkbox">
        <ul>
          <li>general</li>
          <li>notifications</li>
        </ul>
      </li>
    </ul>

    Edit: here is styled, more better looking version based on this silly concept: codepen