Search code examples
cssflexboxcss-gridmultiple-columnsmegamenu

CSS dynamic layout for mega menu that reads in rows or columns depending on number of menu items


I'm doing some prototyping for a mega menu where the menu items should be displayed "smart" where, depending on the number of menu items, they would read either as a row or columns.

The menu items would have children of their own, so they're really like:

 mega menu left text | menu item 1   menu item 2   menu item 3
                     | sublink1 A    sublink2 A    sublink3 A
                     | sublink1 B    sublink2 B
                     | sublink1 C

I'm not including those below for simplicity.

The display would be dependent on the number of menu items. For example, if there were 1-3 menu items, it would read like a row:

mega menu left text | menu item 1   menu item 2   menu item 3

If there were more than 3 menu items, it would read like columns, top down, then to the right, but it still needs to be "smart" in that menu item 4 is in its own column, it isn't underneath menu item 3 (it always needs to maintain 3 columns on the right side):

mega menu left text | menu item 1   menu item 3   menu item 4
                    | menu item 2

5 menu items:

mega menu left text | menu item 1   menu item 3   menu item 5
                    | menu item 2   menu item 4

6 menu items:

mega menu left text | menu item 1   menu item 3   menu item 5
                    | menu item 2   menu item 4   menu item 6

7 menu items:

mega menu left text | menu item 1   menu item 4   menu item 7
                    | menu item 2   menu item 5
                    | menu item 3   menu item 6

8 menu items:

mega menu left text | menu item 1   menu item 4   menu item 7
                    | menu item 2   menu item 5   menu item 8
                    | menu item 3   menu item 6

9 menu items:

mega menu left text | menu item 1   menu item 4   menu item 7
                    | menu item 2   menu item 5   menu item 8
                    | menu item 3   menu item 6   menu item 9

10 menu items:

mega menu left text | menu item 1   menu item 5   menu item 9
                    | menu item 2   menu item 6   menu item 10
                    | menu item 3   menu item 7   
                    | menu item 4   menu item 8

Is there anyway to do this elegantly in css? I thought css columns might do this but it doesn't quite work. Here's a fiddle/demo, if you move the slider on the left you'll see where it falls down:

https://jsfiddle.net/vsdpjwn9/
This uses break-inside: avoid-column; on the <li>

https://jsfiddle.net/3x8scuwt/
This uses display: inline-block; on the <li>

I also tried a separate prototype with flexbox but it seemed to fall down faster so I abandoned it. Seems like css grid would be the same. The thing is, these menu items will be dynamic, there will be a different number of them, each with a different number of sublinks. There probably won't be more than 3 rows, but I don't want to depend on this.

Any css solution or will this require javascript to set custom layout based on the number of individual menu items?

It will need to be responsive as well, but the menu items basically become accordions that stack on top of each other.

Here is one of the fiddle's code:

HTML:

<div id="container">
  <div class="left">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

    <div style="margin-top:1rem;">
      <input id="slider" type="range" min="1" max="8" value="8">
      <p id="slider-output" style="margin-top:0;"></p>
    </div>

  </div>

  <div class="right">
    <ul>
      <li class="1">
          <h4>1 Secondary Content</h4>
          <a href="#a1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#a2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#a3">Lorem ipsum dolora</a><br>
          <a href="#a4">Lorem ipsum dolora</a><br>
          <a href="#a5">Lorem ipsum dolora</a>
      </li>

      <li class="2">
          <h4>2 Secondary Content</h4>
          <a href="#b1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#b2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#b3">Lorem ipsum dolora</a><br>
          <a href="#b4">Lorem ipsum dolora</a><br>
          <a href="#b5">Lorem ipsum dolora</a><br>
          <a href="#b6">Lorem ipsum dolora</a>
      </li>

      <li class="3">
          <h4>3 Secondary Content</h4>
          <a href="#c1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#c2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#c3">Lorem ipsum dolora</a><br>
          <a href="#c4">Lorem ipsum dolora</a>
      </li>

      <li class="4">
          <h4>4 Secondary Content</h4>
          <a href="#d1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#d4">Lorem ipsum dolora</a>
      </li>

      <li class="5">
          <h4>5 Secondary Content</h4>
          <a href="#e1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#e2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#e3">Lorem ipsum dolora</a><br>
          <a href="#e4">Lorem ipsum dolora</a><br>
          <a href="#e5">Lorem ipsum dolora</a>
      </li>

      <li class="6">
          <h4>6 Secondary Content</h4>
          <a href="#f1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#f2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#f3">Lorem ipsum dolora</a><br>
          <a href="#f4">Lorem ipsum dolora</a><br>
          <a href="#f5">Lorem ipsum dolora</a><br>
          <a href="#f6">Lorem ipsum dolora</a>
      </li>

      <li class="7">
          <h4>7 Secondary Content</h4>
          <a href="#g1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#g2">Lorem ipsum dolora</a>
      </li>
      
      <li class="8">
          <h4>8 Secondary Content</h4>
          <a href="#h1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#h2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
          <a href="#h3">Lorem ipsum dolora</a><br>
          <a href="#h4">Lorem ipsum dolora</a><br>
      </li>
    </ul>
  </div>
</div>

CSS:

    #container {
    width: 900px;
    min-height: 400px;
    display: flex;
    gap: 40px;
    padding: 1em;
    border: 1px solid #ccc;
}
.left {
    width: 25%;
    padding-right: 40px;
    border-right: 1px solid #ccc;
}
.right {
    width: 100%;
    margin-left: auto;
}
/*
ul {
  border: 1px solid red;
  height: 400px;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
}
*/

ul {
  list-style: none;
  columns: 3;
  column-gap: 25px;
  margin: 0;
  padding: 0;
  width: 98%;
  border: 1px solid blue;
}

li {
  border: 1px solid red;
  list-style-type: none;
  margin: 0 0 15px 0;
  
  /*display: inline-block;*/
  break-inside: avoid-column;
  
}
li:first-child {
  margin-top: 0;
}

li a {
  display: inline-block;
  margin: .125rem 0;
}

h4 {
  margin: 0;
}

[hidden] {
  display: none !important;
}

JS:

    const slider = document.getElementById('slider');
const output = document.getElementById('slider-output');
const LIs = document.querySelectorAll('li');

output.innerHTML = slider.value;

slider.oninput = function() {
  output.innerHTML = this.value;

  LIs.forEach(li => {
    if (+li.classList.value > slider.value) {
      li.setAttribute('hidden', '');
    } else {
      li.removeAttribute('hidden');
    }
  });
}

Solution

  • As far as I know pure CSS isn't possible without some hacky method using nth-child selector which still requires a limited amount of items. (Also, just so you know, class names with only numbers are not valid.)

    However, this can be done pretty easily with CSS grid and a little JavaScript changing the number of rows.

    The only added complexity is the case when there are only 4 items, by default the last li will be placed at the second column, so it needs to be specified to be at the third column. This can be done either using CSS or JavaScript.

    ul {
        display: grid;
        --row-number: 3;
        grid-template-rows: repeat(var(--row-number), 1fr);
        grid-template-columns: repeat(3, 1fr);
        grid-auto-flow: column;
    }
    
    /*Use CSS for the special case*/
    li:nth-child(4 of :not([hidden])):nth-last-child(-n + 1 of :not([hidden])) {
        grid-column: 3;
    }
    
    function updateColumnNumber() {
        ul.style.setProperty('--row-number', Math.ceil(slider.value / 3));
    }
    

    const slider = document.getElementById('slider');
    const output = document.getElementById('slider-output');
    const LIs = document.querySelectorAll('li');
    const ul = document.getElementById('right-grid');
    output.innerHTML = slider.value;
    
    slider.oninput = function () {
        output.innerHTML = this.value;
    
        LIs.forEach(li => {
            if (+li.classList.value > slider.value) {
                li.setAttribute('hidden', '');
            } else {
                li.removeAttribute('hidden');
            }
        });
        updateColumnNumber();
    }
    
    let li4 = null;
    function updateColumnNumber() {
        /*Or use JS for the special case*/
        /*li4?.style.removeProperty('grid-column');
        if (slider.value == 4) {
            li4 = ul.querySelectorAll("li:not([hidden])")[3];
            li4?.style.setProperty('grid-column', 3)
        }*/
    
        ul.style.setProperty('--row-number', Math.ceil(slider.value / 3));
    }
    
    updateColumnNumber();
    #container {
      width: 900px;
      min-height: 400px;
      display: flex;
      gap: 40px;
      padding: 1em;
      border: 1px solid #ccc;
    }
    
    .left {
      width: 25%;
      padding-right: 40px;
      border-right: 1px solid #ccc;
    }
    
    .right {
      width: 100%;
      margin-left: auto;
    }
    
    ul {
      list-style: none;
      display: grid;
      --row-number: 3;
      grid-template-rows: repeat(var(--row-number), 1fr);
      grid-template-columns: 1fr 1fr 1fr;
      grid-auto-flow: column;
      gap: 25px;
      margin: 0;
      padding: 0;
      width: 98%;
      border: 1px solid blue;
    }
    
    li:nth-child(4 of :not([hidden])):nth-last-child(-n + 1 of :not([hidden])) {
        grid-column: 3;
    }
    
    li {
      box-sizing: border-box;
      border: 1px solid red;
      list-style-type: none;
      margin: 0 0 15px 0;
      /*display: inline-block;*/
      break-inside: avoid-column;
    }
    
    li:first-child {
      margin-top: 0;
    }
    
    li a {
      display: inline-block;
      margin: .125rem 0;
    }
    
    h4 {
      margin: 0;
    }
    
    [hidden] {
      display: none !important;
    }
    <div id="container">
      <div class="left">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    
        <div style="margin-top:1rem;">
          <input id="slider" type="range" min="1" max="10" value="10">
          <p id="slider-output" style="margin-top:0;"></p>
        </div>
    
      </div>
    
      <div class="right">
        <ul id="right-grid">
          <li class="1">
            <h4>1 Secondary Content</h4>
            <a href="#a1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#a2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#a3">Lorem ipsum dolora</a><br>
            <a href="#a4">Lorem ipsum dolora</a><br>
            <a href="#a5">Lorem ipsum dolora</a>
          </li>
    
          <li class="2">
            <h4>2 Secondary Content</h4>
            <a href="#b1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#b2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#b3">Lorem ipsum dolora</a><br>
            <a href="#b4">Lorem ipsum dolora</a><br>
            <a href="#b5">Lorem ipsum dolora</a><br>
            <a href="#b6">Lorem ipsum dolora</a>
          </li>
    
          <li class="3">
            <h4>3 Secondary Content</h4>
            <a href="#c1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#c2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#c3">Lorem ipsum dolora</a><br>
            <a href="#c4">Lorem ipsum dolora</a>
          </li>
    
          <li class="4">
            <h4>4 Secondary Content</h4>
            <a href="#d1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#d4">Lorem ipsum dolora</a>
          </li>
    
          <li class="5">
            <h4>5 Secondary Content</h4>
            <a href="#e1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#e2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#e3">Lorem ipsum dolora</a><br>
            <a href="#e4">Lorem ipsum dolora</a><br>
            <a href="#e5">Lorem ipsum dolora</a>
          </li>
    
          <li class="6">
            <h4>6 Secondary Content</h4>
            <a href="#f1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#f2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#f3">Lorem ipsum dolora</a><br>
            <a href="#f4">Lorem ipsum dolora</a><br>
            <a href="#f5">Lorem ipsum dolora</a><br>
            <a href="#f6">Lorem ipsum dolora</a>
          </li>
    
          <li class="7">
            <h4>7 Secondary Content</h4>
            <a href="#g1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#g2">Lorem ipsum dolora</a>
          </li>
    
          <li class="8">
            <h4>8 Secondary Content</h4>
            <a href="#h1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h3">Lorem ipsum dolora</a><br>
            <a href="#h4">Lorem ipsum dolora</a><br>
          </li>
    
          <li class="9">
            <h4>9 Secondary Content</h4>
            <a href="#h1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h3">Lorem ipsum dolora</a><br>
            <a href="#h4">Lorem ipsum dolora</a><br>
          </li>
    
          <li class="10">
            <h4>10 Secondary Content</h4>
            <a href="#h1">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h2">Lorem ipsum dalora itmet vistafa alotma pastruma</a><br>
            <a href="#h3">Lorem ipsum dolora</a><br>
            <a href="#h4">Lorem ipsum dolora</a><br>
          </li>
        </ul>
      </div>
    </div>