Submit your widget

Pure CSS collapsible tree menu

Created 13 years ago   Views 14223   downloads 2703    Author thecssninja
Pure CSS collapsible tree menu
View DemoDownload
99
Share |

The demo is built using an ordered list (ol) nested with further ordered lists to naturally represent a basic folder structure.

<ol>
    <li class="file"><a href="document.pdf">File 1</a></li>
    <li>
        <label for="subfolder1">Subfolder 1</label>
        <input type="checkbox" id="subfolder1" />
        <ol>
            <li class="file"><a href="">File 2</a></li>
            <li class="file"><a href="">File 2</a></li>
            <li class="file"><a href="">File 2</a></li>
        </ol>
    </li>
</ol>

 

As you can see, in order to get around the general combinator issue in WebKit based browsers I have switched the label to come first then the input so the “folders” could be expanded/collapsed by checking/unchecking the checkbox.

li input
{
    position: absolute;
    left: 0;
    margin-left: 0;
    opacity: 0;
    z-index: 2;
    cursor: pointer;
    height: 1em;
    width: 1em;
    top: 0;
}
li label
{
    background: url(img) 15px 1px no-repeat;
    cursor: pointer;
    display: block;
    padding-left: 37px;
}

 

To sit the input and label in the right visual order I absolutely positioned the input and applied a left padding to the label to push it out. That way changing the styles of the child ol, when the input is checked, can be done using the adjacent sibling combinator. I’ve also set the cursor to pointer when hovered over the input or label to visual show they’re clickable.

li input + ol
{
    background: url(toggle-small-expand.png) 40px 0 no-repeat;
    margin: -0.938em 0 0 -44px; /* 15px */
    display: block;
    height: 1em;
}

 

Unlike my custom radio and checkbox article where I added the background image on the label, this time I had to do some trickery and apply it to the direct sibling ol of an input. Applying a sprite image to the ol wouldn’t be possible in this situation due to it being applied to the ol which would make it difficult to effectively hide other images within the sprite image.

To position the ol correctly I use a negative margin to pull it into the right location so it will sit next to the label and underneath the invisible checkbox.

li input + ol > li
{
    height: 0;
    overflow: hidden;
    margin-left: -14px !important;
    padding-left: 1px;
}

 

To hide the sub folders so they don’t appear when the parent folder is collapsed I target the child list items and set them to a zero height and hide any overflow.

li label
{
    background: url(folder.png) 15px 1px no-repeat;
}
li.file a
{
    background: url(document.png) 0 -1px no-repeat;
    color: #fff;
    padding-left: 21px;
    text-decoration: none;
    display: block;
}

 

To differentiate between folders and files I applied a background image to either the label or to an anchor within a list item for files.

li
{
    position: relative;
    margin-left: -15px;
    list-style: none;
}
li.file
{
    margin-left: -1px !important;
}

 

To pull out the folder list items I apply a larger negative margin so the folder will line up with any of the file icons, and for file based list items I reset the left margin so they sit flush.

Change icon based on file extension

With some CSS3 attribute selectors we can determine an anchor links file format and change the icon accordingly.

li.file a[href $= '.pdf']     { background-position: -16px -1px; }
li.file a[href $= '.html']   { background-position: -32px -1px; }
li.file a[href $= '.css']    { background-position: -48px -1px; }
li.file a[href $= '.js']      { background-position: -64px -1px; }

 

Using the $= CSS attribute selector allows us to check the end of an attribute exactly ends in .pdf, .html etc.

If for some reason you attribute doesn’t end with your file extension, your anchor may have a query string on the end. We can still match file types.

li.file a[href *= '.pdf']   { background-position: -16px -1px; }
li.file a[href *= '.html']  { background-position: -32px -1px; }

 

The *= will match a substring that contains .pdf or .html anywhere within the attribute and if a href has a query string we can still match our file extension without issue. This does have the slight disadvantage that it will match it anywhere e.g. if you have a file called file.html.pdf it will match both file types and the one with the higher CSS specificity will be applied or incase of the example above their CSS specificity is the same so the html background will be applied.

Checkbox attributes

In the demo by default the first folder is open, this is done by adding the checked attribute to the checkbox which will trigger our styles thanks to the checked pseudo-class and reveal its sub files and folders.

We can also add the disabled attribute to the checkbox to stop a user from opening a folder as the input can neither be checked or unchecked.

Lastly using a combination of both disabled and checked will allow us to reveal the sub files and folders but not allow the user to close the top level folder.

 

Browser support

Based on testing this will work in any CSS3 selector supporting browser. The following have been tested and known to work

  • Firefox 1+
  • Opera 9.6+
  • Safari 4+
  • iPhone/iPod Safari
  • Chrome 1+
  • Android
  • IE9+

This could very well work in IE8 but would require some JavaScript to get IE8 to interpret the checked pseudo-class, which I won’t be going into.

Right now I use conditional comments to hide the stylesheet from all versions of IE and another conditional comment to load the stylesheet for IE9 and greater.

<!--[if gte IE 9 ]>
    <link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<![endif]-->
<!--[if !IE]>-->
    <link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<!--<![endif]-->

Highly scalable

This technique will cater for a large amount of sub folders and files. It’s governed by your screen real estate and even then it’ll apply scroll bars to the document when the tree structure gets too long or wide.

Tag: tree menu