Can you believe that the treeview on the image below does not use any JavaScript, but relies only on CSS3?
Today I’m going to show you a really easy and absolutely JavaScript-less solution for creating multi-level treeviews with minimal markup and with the power of CSS3. Basically what we need is a simple list:
<div class="css-treeview"> <ul></ul> </div>
… With any number of nested items and subtrees. Each item that is going to be a container of a subtree should be supplied with a label and a checkbox.
Normal (non-clickable) item:
<li><a href="./">item</a></li> <li><a href="./">item</a></li>
Expandable (clickable) submenu item:
<li><input type="checkbox" id="item-0" /><label for="item-0">folder</label> <ul> <li><input type="checkbox" id="item-0-0" /><label for="item-0-0">folder</label> <ul> <li><input type="checkbox" id="item-0-0-0" /><label for="item-0-0-0">folder</label> <ul> <li><a href="./">item</a></li> <li><a href="./">item</a></li> <li><a href="./">item</a></li> <li><a href="./">item</a></li> </ul> </li> </ul> </li> </ul> </li>
Making it work is pretty simple and straightforward. All we need is to query the normal and :checked states of the checkboxes and set the adjacent <ul /> element to visible or hidden in the stylesheet:
Unchecked checkbox:
.css-treeview input + label + ul
{
display: none;
}
Selected checkbox:
.css-treeview input:checked + label + ul
{
display: block;
}
<pre>
For aesthetic purposes we hide the checkboxes like this:
.css-treeview input
{
position: absolute;
opacity: 0;
}
It is recommended to hide the checkbox with position/opacity rather than with display: none, otherwise you will have problems with certain browsers.
If your treeview is going to contain disabled subtrees, you can utilize the :not() selector, so disabled items with submenus will not be clickable:
.css-treeview input:checked:not(:disabled) + label + ul
{
display: block;
}
The visual representation of the disabled items can be accomplished by changing the opacity and the cursor of the label element:
.css-treeview input:disabled + label
{
cursor: default; /* or no-drop */
opacity: .6;
}
Finally, for the +/- buttons on the left, we can use generated content with the :before pseudo element:
.css-treeview label,
.css-treeview label::before
{
background: url("icons.png") no-repeat;
}
.css-treeview label::before
{
content: "";
width: 16px;
margin: 0 22px 0 0;
vertical-align: middle;
background-position: 0 -32px;
}
.css-treeview input:checked + label::before
{
background-position: 0 -16px;
}
All of the icons that have been used in the example are in the icons.png sprite. The solution will work with any browser supporting the adjacent element selector (E + E) and the attribute selectors of CSS3. Do not forget that the values of the for/id attribute pairs for the label and checkbox elements should be unique, otherwise the treeview will not work. Tab/Spacebar navigation is supported natively. The demo is availble on this page, or you can download it straight away from this link.
Of course this is just the basic concept that you can use for inspiration. A few ideas that can bring you even further:
- By adding the disabled attribute to a checkbox in your markup, the nested subtree will become expanded by default.
- You can use the cool CSS3 transitions to achieve cool behaviors.
- You can use additional classes to display different “folder” items.
- You can use :hover in order to show the pluses and minuse only when the treeview is hovered.
- You can use an additional class name that will show the pluses and minuses only when applied.
- You can use the attribute selectors to display different icons for each normal item according to the extension in the href attribute, for example:
li a[href$=".js"]
{
background: url("js.png") no-repeat center;
}
li a[href$=".css"]
{
background: url("css.png") no-repeat center;
}
View the demo or download it here. Find more experiments here.

nicing :)
thanks :)
Amazing!
[...] CSS3 can do some amazing things (e.g. http://acidmartin.wordpress.com/2011/09/26/css3-treevew-no-javascript/) [...]
[...] Martin Ivanov demonstrates, using HTML checkboxes and the CSS :checked pseudo-class, how to make an expandable tree view without JavaScript [...]
Works with Google Chrome v18 but not with v19(current) or v20(beta) – the branches do not expand/collapse when clicked with these chrome versions on Windows 7/Server 2008 R2. Google Chrome auto-updated a day or two ago to v19.0.1084.46 and since then it has no longer worked. Safari and iOS confirmed working OK.
Thanks for reporting this, Richard. Will check what the problem may be.
Hey Richard, once again – thank you very much for pointing out this issue. It is now fixed and the updated code is available for download.
Yep, used the “~ ul” trick in my css as per your latest download and now works perfect with Chrome v19. Thanks for updating so quickly!
You are welcome, Richard. Actually I should have used the general sibling selector from the beginning, it’s more flexible and allows markup changes.
…just as a side note, you mention using CSS3 transitions. The most obvious application for this would be to smoothly transition when branches are expanded/collapsed. I’m not sure how this can be achieved when switching between display: none and display: block in CSS as changing the display element seems to break transitions. Any ideas?
You can try to do some opacity transition, combined with position absolute in the initial state, when opacity is 0.