I'm trying to create a tree-like <select>
using HTML and CSS.
To maintain accessibility I'd like to avoid javascript if possible.
I'd also like to avoid using
instead of padding, as this prevents pressing letter keys to jump to items.
What I have so far is this:
<select>
<optgroup label="fluffy" style="padding-left: 10px;"></optgroup>
<optgroup label="kitties" style="padding-left: 20px;"></optgroup>
<option value="1" style="padding-left: 30px;">Fluffykins</option>
<option value="2" style="padding-left: 30px;">Mr Pooky</option>
<optgroup label="puppies" style="padding-left: 20px;"></optgroup>
<option value="3" style="padding-left: 30px;">Doggins</option>
<optgroup label="not fluffy" style="padding-left: 10px;"></optgroup>
<optgroup label="snakes" style="padding-left: 20px;"></optgroup>
<option value="4" style="padding-left: 30px;">Fingers</option>
<optgroup label="crabs" style="padding-left: 20px;"></optgroup>
<option value="5" style="padding-left: 30px;">Lucky (AKA Citizen Snips)</option>
</select>
This works fine in Firefox, but IE ignores the padding, rendering it as a flat list (quite hard to use) and Chrome doesn't render the <optgroup>
s, which are technically not valid as an <optgroup>
is supposed to contain at least on <option>
.
Unfortunately <optgroup>
s can't be nested.
This is how Firefox renders it
You can use unordered lists to create a hierarchy (the <ul> and <li> tags). This can give you a tree structure which is semantically valid, and on top of that you can do whatever you like to express it in interesting ways, either through CSS or Javascript.
With the SELECT element it will not work. You can create a custom SELECT based on this:
css:
* {
margin: 0;
padding: 0;
}
.select, .select-tree {
border: 1px solid black;
width: 200px;
overflow: hidden;
list-style-type: none;
margin: 10px;
}
.select .group-label {
background-color: white;
}
.select .option-label {
background-color: white;
display: block;
}
.select .hidden-checkbox {
display: none;
}
.select .hidden-checkbox:checked + .option-label,
.select-tree .hidden-checkbox:checked ~ .group-children,
.select-tree .hidden-checkbox:checked ~ .group-children * {
background-color: lightblue;
}
.select-tree .group-children {
list-style-type: none;
padding-left: 20px;
}
.select-tree .option-label {
padding-left: 5px;
}
.select-list .level-0 {
padding-left: 10px;
}
.select-list .level-1 {
padding-left: 20px;
}
.select-list .level-2 {
padding-left: 30px;
}
html:
select many from list (groups not selectable):
<ol class="select select-list select-many">
<li>
<input name="list1[1]" type="checkbox" id="check1" class="hidden-checkbox"/>
<label for="check1" class="option-label level-0">option 1</label>
</li>
<li>
<span class="group-label level-0">group 1</span>
</li>
<li>
<input name="list1[2]" type="checkbox" id="check2" class="hidden-checkbox"/>
<label for="check2" class="option-label level-1">option 2</label>
</li>
<li>
<span class="group-label level-1">group 2</span>
</li>
<li>
<input name="list1[3]" type="checkbox" id="check3" class="hidden-checkbox"/>
<label for="check3" class="option-label level-2">option 3</label>
</li>
<li>
<input name="list1[4]" type="checkbox" id="check4" class="hidden-checkbox"/>
<label for="check4" class="option-label level-2">option 4</label>
</li>
<li>
<input name="list1[5]" type="checkbox" id="check5" class="hidden-checkbox"/>
<label for="check5" class="option-label level-0">option 5</label>
</li>
</ol>
select one from list (groups not selectable):
<ol class="select select-list select-one">
<li>
<input name="list2" type="radio" id="check6" class="hidden-checkbox"/>
<label for="check6" class="option-label level-0">option 1</label>
</li>
<li>
<span class="group-label level-0">group 1</span>
</li>
<li>
<input name="list2" type="radio" id="check7" class="hidden-checkbox"/>
<label for="check7" class="option-label level-1">option 2</label>
</li>
<li>
<span class="group-label level-1">group 2</span>
</li>
<li>
<input name="list2" type="radio" id="check8" class="hidden-checkbox"/>
<label for="check8" class="option-label level-2">option 3</label>
</li>
<li>
<input name="list2" type="radio" id="check9" class="hidden-checkbox"/>
<label for="check9" class="option-label level-2">option 4</label>
</li>
<li>
<input name="list2" type="radio" id="check10" class="hidden-checkbox"/>
<label for="check10" class="option-label level-0">option 5</label>
</li>
</ol>
select one from tree (groups selectable):
<ol class="select select-tree select-one">
<li>
<input name="tree1" type="radio" id="check11" class="hidden-checkbox"/>
<label for="check11" class="option-label">option 1</label>
</li>
<li>
<input name="tree1" type="radio" id="check12" class="hidden-checkbox"/>
<label for="check12" class="option-label group-label">group 1</label>
<ol class="group-children">
<li>
<input name="tree1" type="radio" id="check13" class="hidden-checkbox"/>
<label for="check13" class="option-label">option 2</label>
</li>
<li>
<input name="tree1" type="radio" id="check14" class="hidden-checkbox"/>
<label for="check14" class="option-label group-label">group 2</label>
<ol class="group-children">
<li>
<input name="tree1" type="radio" id="check15" class="hidden-checkbox"/>
<label for="check15" class="option-label">option 3</label>
</li>
<li>
<input name="tree1" type="radio" id="check16" class="hidden-checkbox"/>
<label for="check16" class="option-label">option 4</label>
</li>
</ol>
</li>
</ol>
</li>
<li>
<input name="tree1" type="radio" id="check17" class="hidden-checkbox"/>
<label for="check17" class="option-label">option 5</label>
</li>
</ol>
IE9+ only (and you have to give the doctype for msie)... I think you cannot do it without "level-x" css classes, because by nested divs the padding would offset the colored background of the label too...
By IE8- you have to use javascript to colorize the labels...
As you've noted, you can't nest one OPTGROUP
within another. But you do have to enclose them. This will achieve at least the base level of indenting you're not already seeing.
<optgroup label="fluffy" style="padding-left: 10px;">
<optgroup label=" kitties" style="padding-left: 20px;">
<option value="1" style="padding-left: 30px;">Fluffykins</option>
<option value="2" style="padding-left: 30px;">Mr Pooky</option>
</optgroup>
<optgroup label=" puppies" style="padding-left: 20px;">
<option value="3" style="padding-left: 30px;">Doggins</option>
</optgroup>
</optgroup>
Since you can't jump to the OPTGROUP
headings with the keyboard anyway (and only to the actual OPTION
), there should no problem padding the label
out with
to work across the cross-browser issues on padding.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With