HTML / CSS: Nested <options> in a <select> field?

You cannot nest multiple <option>s. If you want to group <option> elements, use <optgroup>.


You can use <optgroup> to create a single level of nesting...

<select>
  <optgroup label="Options 1">
    <option>Option 1.1</option>
    <option>Option 1.2</option>
  </optgroup>
  <optgroup label="Options 2">
    <option>Option 2.1</option>
    <option>Option 2.2</option>
  </optgroup>
</select>

Note that the group labels are not selectable options. In that case, I would recommend using the text-indent solution that is mentioned in the top answer to the question that home linked to in his comment.


Nested Accordeon select

I made this approach since I couldn´t find what I was searching. A nested accordeon select. Its CSS is very simple and can be improved. The only thing you need is an object with keys and values you want to add into the select. Keys would be subgroups, and key values (arrays and single elements) would be selectable items.

Once you have your array, only thing you need to do is to call

initAccordeon(obj);

with your data object as an argument, and the nested accordeon will appear:

const obj = {
  Cars: {
    SwedishCars: [
      "Volvo",
      "Saab"
    ],
    GermanCars: [
      "Mercedes",
      {
        Audi: [
          "Audi A3", 
          "Audi A4", 
          "Audi A5" 
        ]
      }
    ]
  },
  Food: {
    Fruits: [
      "Orange",
      "Apple",
      "Banana"
    ],
    SaltyFoods: [
      "Pretzels",
      "Burger",
      "Noodles"
    ],
    Drinks: "Water"
  }
};

initAccordeon(obj);   // <--------------------------- Call initialization

function accordeonAddEvents() {
  Array.from(document.getElementsByClassName("accordeon-header")).forEach(function(header) {
    if (header.getAttribute("listener") !== "true") {
      header.addEventListener("click", function() {
        this.parentNode.getElementsByClassName("accordeon-body")[0].classList.toggle("hide");
      });
      header.setAttribute("listener", "true");
    }
  });

  Array.from(document.getElementsByClassName("button-group")).forEach(function(but) {
    if (but.getAttribute("listener") !== "true") {
      but.addEventListener("click", function() {
        if (this.getAttribute("depth") === "-1") {
          let header = this;
          while ((header = header.parentElement) && header.className !== "accordeon");
          header.getElementsByClassName("accordeon-header")[0].innerHTML = this.innerHTML;
          return;
        }
        const groups = Array.from(this.parentNode.getElementsByClassName("accordeon-group"));
        groups.forEach(g => {
          if (g.getAttribute("uuid") === this.getAttribute("uuid") &&
            g.getAttribute("depth") === this.getAttribute("depth")) {
            g.classList.toggle("hide");
          }
        });
      });
      but.setAttribute("listener", "true");
    }
  });
}

function initAccordeon(data) {
  accordeons = Array.from(document.getElementsByClassName("accordeon-body"));
  accordeons.forEach(acc => {
    acc.innerHTML = "";
    const route = (subObj, keyIndex = 0, parent = acc, depth = 0) => {
      const keys = Object.keys(subObj);
      if (typeof subObj === 'object' && !Array.isArray(subObj) && keys.length > 0) {
        while (keyIndex < keys.length) {
          var but = document.createElement("button");
          but.className = "button-group";
          but.setAttribute("uuid", keyIndex);
          but.setAttribute("depth", depth);
          but.innerHTML = keys[keyIndex];
          var group = document.createElement("div");
          group.className = "accordeon-group hide";
          group.setAttribute("uuid", keyIndex);
          group.setAttribute("depth", depth);
          route(subObj[keys[keyIndex]], 0, group, depth + 1);
          keyIndex++;
          parent.append(but);
          parent.append(group);
        }
      } else {
        if (!Array.isArray(subObj)) subObj = [subObj];
        subObj.forEach((e, i) => {
          if (typeof e === 'object') {
              route(e, 0, parent, depth);
          } else {
              var but = document.createElement("button");
              but.className = "button-group";
              but.setAttribute("uuid", i);
              but.setAttribute("depth", "-1");
              but.innerHTML = e;
              parent.append(but);
          }
        });
      }
    };
    route(data);
  });
  accordeonAddEvents();
}
.accordeon {
  width: 460px;
  height: auto;
  min-height: 340px;
  font-size: 20px;
  cursor: pointer;
  user-select: none;
  -moz-user-select: none;
  -khtml-user-select: none;
  -webkit-user-select: none;
  -o-user-select: none;
  display: block;
  position: relative;
  z-index: 10;
}

.accordeon-header {
  display: inline-block;
  width: 450px;
  border: solid 0.1vw black;
  border-radius: 0.2vw;
  background-color: white;
  padding-left: 10px;
  color: black;
}

.accordeon-header:hover {
  opacity: 0.7;
}

.accordeon-body {
  display: block;
  position: absolute;
}

.button-group {
  display: block;
  cursor: pointer;
  width: 460px;
  text-align: left;
  font-size: 20px;
  font-weight: bold;
}

.accordeon-group {
  padding-left: 20px;
}

.accordeon-group .button-group {
  width: 100%;
}

.button-group[depth="-1"] {
  color: green;
}

.hide {
  display: none;
}
<div class="accordeon">
  <span class="accordeon-header">Select something</span>
  <div class="accordeon-body hide">
      
  </div>
</div>

Tags:

Html

Css