Turning Lists into Trees
In this article, I will present a technique to display a multi-level unordered list in the form of a tree with lines connecting nodes. The final result will look similar to this:
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
Note that the tree that we're going to build will be absolutely static – we won't be able to expand/collapse its nodes or anything like that.
Our intention is to avoid cluttering the HTML code with unnecessary tags. Ideally, the code would be as simple as this:
<ul class="tree"> <li>Animals <ul> <li>Birds</li> <li>Mammals <ul> <li>Elephant</li> <li>Mouse</li> </ul> </li> . . . </ul>
So it's just a regular multi-level unordered list, with no
additional elements. The only thing that indicates it's a tree is
the tree
class added to the first-level
<ul>
element. Without styling, the above code
produces a familiar result:
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
Bullets Be Gone
The first thing that we need to do is get rid of the ugly bullets.
We accomplish this by setting the list-style-type
property to none
.
ul.tree, ul.tree ul { list-style-type: none; }
This is what we get:
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
Drawing Vertical Lines
We need to draw a vertical line along the side of every list, regardless of its nesting level. For that purpose, we'll use a tiny (10 pixels high) image of a vertical line.
vline.png
We put the image in the background of the <ul>
element
and add the repeat-y
value, which causes the image to be repeated along
the y-axis, creating a vertical line as high as the list itself. We also adjust the
margin
and padding
properties of the list and list items
(<li>
) so that the tree is nicely indented.
ul.tree, ul.tree ul { list-style-type: none; background: url(vline.png) repeat-y; margin: 0; padding: 0; } ul.tree ul { margin-left: 10px; } ul.tree li { margin: 0; padding: 0 12px; }
The result:
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
Connecting the Nodes
We place an image of a T-shaped line segment in the background of each list item.
node.png
We also set the line-height
property to a value equal to the
height of the image. For a slightly more attractive look, we set the color of the
list item text to #369
and make the font bold.
ul.tree, ul.tree ul { list-style-type: none; background: url(vline.png) repeat-y; margin: 0; padding: 0; } ul.tree ul { margin-left: 10px; } ul.tree li { margin: 0; padding: 0 12px; line-height: 20px; background: url(node.png) no-repeat; color: #369; font-weight: bold; }
Let's see the result:
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
Last but not Least
Obviously, the last node in each list requires special treatment. Firstly, it needs to have a different background image – an L-shaped line segment:
lastnode.png
Secondly, the vertical line in the background of the list must end at the last node. We can accomplish this by setting the background color of the last node to white (or whatever other color we're using). The background of the item is placed over the background of the list, so it will cover the last segment of the vertical line.
ul.tree, ul.tree ul { list-style-type: none; background: url(vline.png) repeat-y; margin: 0; padding: 0; } ul.tree ul { margin-left: 10px; } ul.tree li { margin: 0; padding: 0 12px; line-height: 20px; background: url(node.png) no-repeat; color: #369; font-weight: bold; } ul.tree li.last { background: #fff url(lastnode.png) no-repeat; }
The browser does not know which nodes are "last", so it's our responsibility to
tell it. We can either explicitly add the last
class to the last node of
every list:
<ul class="tree"> <li>Animals <ul> <li>Birds</li> <li>Mammals <ul> <li>Elephant</li> <li class="last">Mouse</li> </ul> </li> <li class="last">Reptiles</li> . . . </ul>
...or be lazy and have JavaScript do that for us:
<script type="text/javascript"> window.onload = function () { var tree = document.getElementById("tree"); var lists = [ tree ]; for (var i = 0; i < tree.getElementsByTagName("ul").length; i++) lists[lists.length] = tree.getElementsByTagName("ul")[i]; for (var i = 0; i < lists.length; i++) { var item = lists[i].lastChild; while (!item.tagName || item.tagName.toLowerCase() != "li") item = item.previousSibling; item.className += " last"; } }; </script> . . . <ul class="tree" id="tree"> <li>Animals <ul> <li>Birds</li> <li>Mammals <ul> <li>Elephant</li> <li>Mouse</li> </ul> </li> . . . </ul>
Time to see the final result - voilá!
- Animals
- Birds
- Mammals
- Elephant
- Mouse
- Reptiles
- Plants
- Flowers
- Rose
- Tulip
- Trees
- Flowers
You might notice that adding the last
class to list items is
in conflict with our original idea of not cluttering up the HTML code.
Actually, there is an elegant solution to this problem — the
:last-child
pseudo-class. If we were to use it, we would replace the last
class
selector with:
ul.tree li:last-child { background: #fff url(lastnode.png) no-repeat; }
Sadly, the pseudo-class is defined in the upcoming CSS3 specification and at the moment few web browsers support it.
Appendix: The jQuery way
JQuery fans, which includes myself, could rewrite the above JavaScript code as:
<script type="text/javascript"> $(document).ready(function () { $('ul.tree li:last-child').addClass('last'); }); </script>