In this article I’ll explain how to format a directory listing by using CSS. Usually a directory listing looks as follows.

The markup used in the above directory listing is obsolete (HTML 3.2), non-semantical and with a low level of accessibility. Apache, the web server taken as example, uses an unique pre
element to wrap the entire content of the listing and to format such content as a table. Our goal is simple: we want to format a directory listing by using CSS for the presentation and semantical XHTML for the structure, preserving at the same time the original layout.
Table of contents
- Choosing the right markup
- Basic styles
- Styling the table
- Adding background images
- Finalizing the layout
- Bulletproofing the layout
- Download examples
Choosing the right markup
Since our data in the original layout are formatted as a table, we’ll use a real table in order to give to our users all the accessibility benefits related to table elements, in particular to the users of screen readers and other assistive technologies. The proposed markup is the following:
Listing 1. Proposed markup for a directory listing
<table summary="Directory listing of /directory">
<tr>
<th scope="col">Name</th>
<th scope="col">Last Modified</th>
<th scope="col">Size</th>
<th scope="col">Description</th>
</tr>
<tr>
<td><a href="#" title="Parent Directory">Parent Directory</a></td>
<td>22-Dec-2007 05:55</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td><a href="#">file.html</a></td>
...omission...
</tr>
<tr>
<td><a href="#">file.pdf</a></td>
...omission...
</tr>
<tr>
<td><a href="#">file.jpg</a></td>
...omission...
</tr>
<tr>
<td><a href="#">file.zip</a></td>
...omission...
</tr>
<tr>
<td><a href="#">file.mp3</a></td>
...omission...
</tr>
<tr>
<td><a href="#" title="Go to 'Documents'">documents</a></td>
<td>10-Dec-2007 09:00</td>
<td>-</td>
<td>-</td>
</tr>
</table>
As you can see, we’ve used two important attributes of table elements, namely summary
and scope
. The first adds a brief description of table contents and will be spoken aloud by screen readers. The latter associates each table header with its respective column and is very useful for table navigation. We can appreciate this new, semantical structure in the following screenshot taken from Lynx.

Basic styles
We start with very simple styles.
Listing 2. Basic styles
body {
background: #fff;
color: #000;
}
a:link, a:visited {
color: blue;
background: transparent;
}
Note that we haven’t specified a different font family and hyperlinks color for the page, since we want to respect the default rendering of each browser.
Styling the table
We want to create a fluid table with top and bottom borders. These borders will replace the presentational hr
element used in the original layout. The styles are the following:
Listing 3. Styling the table
table {
font-family: monospace;
border: none;
width: 90%;
border-collapse: collapse;
}
td, th {
width: 25%;
vertical-align: top;
padding-top: 0.3em;
padding-bottom: 0.3em;
}
th {
font-weight: normal;
text-align: left;
}
.bordertop {
border-top: 2px outset #ccc;
}
.borderbottom {
border-bottom: 2px outset #ccc;
}
First, we specify a generic monospaced font on the table. Again, we want to respect the default rendering of each browser. The width of our table is fluid, and the borders of its cells won’t have a blank space between them, since we’ve set the border-collapse
property to collapse
. The content of each cell will be vertically aligned on top and there will be a certain amount of top and bottom padding. The third rule resets the default font weight and text alignment of each table header. Finally, the last two rules create the effect of two horizontal rules thanks to a top and a bottom border, respectively. Since we want that these borders appear after the first and last row, we can’t specify them on the table itself.
Adding background images
Now we need to add a different background image to each type of file. The code is pretty simple.
Listing 4. Adding background images
.parent, .text, .pdf, .image, .compressed, .sound, .dir {
padding-left: 24px;
height: 1.5em;
}
.parent {
background: transparent url("img/back.gif") no-repeat top left;
}
.text {
background: transparent url("img/text.gif") no-repeat top left;
}
.pdf {
background: transparent url("img/pdf.gif") no-repeat top left;
}
.image {
background: transparent url("img/image.gif") no-repeat top left;
}
.compressed {
background: transparent url("img/compressed.gif") no-repeat top left;
}
.sound {
background: transparent url("img/sound.gif") no-repeat top left;
}
.dir {
background: transparent url("img/dir.gif") no-repeat top left;
}
The relevant rule here is the first. By specifying a proportional height and a left padding on each cell, we assure ourself that the background images will be entirely visible. The other rules simply position the background images on the left top edge of each cell.
Finalizing the layout
At the very end of each directory listing there is an address
element containing the relevant information about the web server and the host. The styles for this element are the following.
Listing 5. Finalizing the layout
address {
margin: 0;
padding: 0.5em 0;
}
We use vertical padding instead of margins in order to avoid some browsers oddities in the calculations of collapsing margins.
Bulletproofing the layout
To add more flexibility to our layout, we can write the following code.
Listing 6. Bulletproofing the layout
.parent, .text, .pdf, .image, .compressed, .sound, .dir {
padding-left: 1.8em;
}
By using ems, the left padding of these cells will scale along with text while the user changes the default font size of the page.