16

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 &nbsp; 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

Mat
  • 202,337
  • 40
  • 393
  • 406
Greg
  • 316,276
  • 54
  • 369
  • 333

4 Answers4

6

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...

inf3rno
  • 24,976
  • 11
  • 115
  • 197
  • I would at least use nested
      or
      tag to maintain some semantics.
    – Luke H Jun 03 '13 at 13:17
  • I modified the example, and added a tree with selectable groups too. – inf3rno Jun 04 '13 at 21:59
  • 1
    You'd have to implement some javascript to maintain usability, as selects are supposed to be keyboard interactable. – Wing Jul 03 '14 at 11:23
  • Yepp, that's right. Actually you can add tabindex to the label, so the tabulator works, but somehow space or enter does not work on the label, just on the input. I guess this is a browser bug, because mouse click works on the label... – inf3rno Jul 03 '14 at 13:01
  • I added [a firefox bug report](https://bugzilla.mozilla.org/show_bug.cgi?id=1035790). I'm waiting for a response, and if they fix it, I'll send a bug report to the dev team of the other browsers. Tabindex does not work on labels with chrome. By msie it scrolls to the label by pressing space. – inf3rno Jul 08 '14 at 13:10
2

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="&nbsp;&nbsp;&nbsp;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="&nbsp;&nbsp;&nbsp;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 &nbsp; to work across the cross-browser issues on padding.

Community
  • 1
  • 1
random
  • 9,774
  • 10
  • 66
  • 83
0

You have to wrap the option-Tags with the optgroup-Tags.

It should look like this:

        <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>

Hope it helps :)

ChrisBenyamin
  • 1,718
  • 4
  • 22
  • 39
  • 1
    Hm? What do you mean? Look at W3C, then you'll see an example how you should use optgroup: http://www.w3.org/WAI/PF/select-proposal.html – ChrisBenyamin Aug 06 '09 at 00:16
  • What you've linked to is a proposal that never made the spec. In HTML 4.01 optgroups can't nest – Greg Sep 25 '09 at 14:25
0

It is not a really good solution, but have you tried intending the elements with non breaking spaces (& nbsp;) ?

Oliver Hanappi
  • 12,046
  • 7
  • 51
  • 68