CSS Swag: Multi-Column Lists
Issue № 204

CSS Swag: Multi-Column Lists

One of the minor holy grails of XHTML and CSS is to produce a single, semantically logical ordered list that wraps into vertical columns.

Article Continues Below

The ideal situation, in my view, would be a single XHTML list whose wrapping is controlled entirely by CSS. Further, the wrapped list should tolerate text resizing (easily accomplished by styling everything in ems).

CSS and the browsers that support it don’t yet provide us with “vertical wrap,” so we have to augment basic list markup with additional attributes and styling rules to achieve the effect. I’ll take you there—or as close as we can get using today’s browsers—but along the way let’s look at a variety of ways to accomplish a similar effect.

We’ll be shooting for something that looks a bit like this:

An ordered list styled in vertical columns

(Why, you’ve doubtless been pondering, is this article entitled “CSS Swag”?  In hunting for a metaphor for the multi-column list styling shown in the above screenshot, I pictured a strip of cloth that hung down, drew back up to its initial height, then draped again…  A quick google led me to this wonderful term, which I had previously associated only with an Australian tramp’s bundle of possessions, and to a great etymology that links swagger to sway.)

Watch your step#section1

I’ll warn you up front. If you want to present a list in multiple columns, you’ll have to compromise. You can sacrifice W3C web standards and use deprecated markup, you can live with markup that’s less than semantically logical, you can tolerate a mixture of presentation with content, you can say goodbye to browser compatibility, or you can use markup that’s heavy with attributes and styling that’s heavy with rules. Every road exacts a toll.

While each of these methods is simple enough to set up, the acid test comes when you add or remove list items in the course of website maintenance. How easy to modify is your chosen method? Ideally, we should be able to simply insert or delete list items and watch the list rewrap itself.

The reality is not so ideal. Unlike the horizontal wrap that our browsers handle automatically, vertical wrapping requires us to explicitly state which list items occur in which columns or where the columns should break. To keep a modified list wrapping properly, we must rearrange the list items, change classes and attributes in the markup, or tweak rules in the stylesheet.  The only technique described here that doesn’t need any of that fuss (Method 1) has other serious behavioral problems.  Finally, because vertical proportions are so important here, most of these lists are going to break if we assume that each list item will occupy one line, only to have some items wrap to two or more lines.

So why do we bother?  Well, because the final effect is so cool—and practical.  Wrapping a list into columns can relieve the website visitor of the necessity to scroll down a long list.  A three- or four-column list can fill the width of a page while a single skinny column could leave the layout looking anemic.  There are as many reasons to wrap lists as there are web designers:  very definitely More Than A Few.

To work, then.

First, expunge all white space#section2

The default rendering of an XHTML ordered list in browsers is that of a single vertical series of items:

  <ol>
  <li>Aloe</li>
  <li>Bergamot</li>
  <li>Calendula</li>
  </ol>

Default rendering of ordered lists

To bypass some browser inconsistencies, I’ve taken to marking up my lists like this:

  <ol
    ><li>Aloe<li>Bergamot</li
    ><li>Calendula</li
  ></ol>

I’ve moved the last closing angle-bracket > of each row to the beginning of the next row.  This keeps each list item on a row by itself for the benefit of human readers and at the same time effectively eliminates all of the white space between tags, this producing more consistent rendering across browsers.

(My pet theory as to why Internet Explorer includes the white space between list-item tags in its rendering calculations is that it’s a hold-over from that prehistoric era when list items, like table cells, didn’t have closing tags.  Back then, a browser properly paid attention to all text including white-space from one start-tag <li> to the next.  When closing tags were added to the mix, apparently no one at Microsoft remembered—or deemed it important enough—to adjust the logic to stop parsing between one closing tag </li> and the next start-tag <li>.)

Also, I’m adding a hyperlink to each list item.  This allows me to check for abnormalities in rendering anchors when various CSS rules are applied:

    ><li><a href="#">Aloe</a></li

Note: most of my examples use ordered lists because item sequence is paramount in this exercise; unordered lists may, of course, be substituted.

And one more thing: the example pages attached to this article are all marked up with a strict doctype. I use this in all my web work these days because it appears that I can get more consistent results cross-browser with less weeping and gnashing of teeth.  If you use a transitional doctype or run in quirks mode, you may need to tweak the CSS to get these methods to behave cross-browser.

Method 1: Floating list items#section3

Of all of the techniques I’m going to describe, this one uses the cleanest XHTML markup and some of the simplest CSS.  Unfortunately, its flaws prevent it from becoming my method of choice.

Method 1: Floating list items

See Example 1.

The technique is simple: give the list items a fixed width and float them left.

The list items wrap horizontally like words in a paragraph.  Generally speaking, when a series of blocks are floated left or right, they align horizontally and wrap around when they reach the maximum width of their container.  If just three items can fit on one row, as in this example, the list naturally wraps into rows of three columns.

The XHTML markup is a straightforward list with no special classes or other attributes required.  To prevent subsequent page elements from being affected by the float I’ve contained the list in a <div> and cleared the float with a (non-semantic) <br />:

<div class="wrapper">
  <ol
    ><li><a href="#">Aloe</a></li
    ><li><a href="#">Bergamot</a></li
    ...
    ><li><a href="#">Oregano</a></li
    ><li><a href="#">Pennyroyal</a></li
  ></ol>
  <br />
</div><!-- .wrapper -->

The essential CSS is brief:

  /* allow room for 3 columns */
  ol
  {
    width: 30em;
  }  /* float & allow room for the widest item */
  ol li
  {
    float: left;
    width: 10em;
  }  /* stop the float */
  br
  {
    clear: left;
  }  /* separate the list from subsequent markup */
  div.wrapper
  {
    margin-bottom: 1em;
  }

How to edit: items can be added or removed with no further changes to the XHTML or CSS.

This method fits two of our criteria for an ideal solution. The list itself is a simple, single XHTML list and the column-wrapping is controlled entirely from the stylesheet. However, it falls short of our goal because the list sequence runs across and then down instead of descending in vertical columns.  Also, it doesn’t survive well cross-browser: Internet Explorer and Opera suppress the item markers (ol numbers and ul bullets) when list items are floated left or right.

Method 2: Numbering split lists with HTML attributes#section4

This tactic—perhaps born of a desperate desire to tame web design to be as obedient as print design—is to split the list into multiple sub-lists and arrange them side by side.

Method 2: Numbering split lists in XHTML

See Example 2.

This approach has several flaws: the semantic integrity of the single list is sacrificed; list wrap, which I consider presentational, is determined entirely by the HTML markup rather than by the stylesheet; and item numbering resets to “1” with each new list.

That last hurdle, at least, can be leapt over if you don’t mind using deprecated markup.  (In order for this markup to validate, you’ll need to use a transitional doctype.)

As Example 2 demonstrates, the now-deprecated HTML attributes start and value let us reset list numbering.  If we ignore the W3C recommendation and use deprecated markup, we can mark up separate lists to give the illusion of one continuous sequence.

Here’s an example I’ve pared down to highlight the attribute placement:

  <ol
    ><li>item</li
    ><li>item</li
  ></ol>  <ol start="3" 
    ><li>item</li
    ><li>item</li
  ></ol>  <ol
    ><li value="5">item</li
    ><li>item</li
    ><li>item</li
  ></ol>

These list items will be numbered sequentially 1–6 by browsers and other user agents that still support the deprecated markup.

The stylesheet’s only job, then, is to float the sub-lists side by side, then clear the float after the final column.  (In them olden days before we got religion, sub-lists were commonly positioned inside adjacent table cells.)

How to edit: When list items are added or deleted, some items will need to be moved from one sub-list to another to maintain a consistent column length.  If the number of items per column changes, the start and value attribute values in XHTML will need to be tweaked to maintain proper numbering.  No CSS changes are required.

Method 3: Numbering split lists with CSS generated content#section5

If you’re splitting your list into chunks, another tool for numbering the items contiguously is CSS content generation, wherein a stylesheet causes text to appear that doesn’t exist in the XHTML markup.

Method 3: Numbering split lists with CSS

See Example 3.

List item numbering and bulleting are actually two instances of content-generation, but they’re the only ones that are supported universally, probably because they date back to early HTML before CSS was born.  The other forms of CSS content-generation are ill-supported in today’s browsers: of those tested, only Opera rendered this example properly.

The XHTML markup is similar to that in Method 2 but even cleaner: the list is divided into two or more sub-lists, but the start and value attributes are omitted.

Using CSS we suppress normal list item numbering, then apply the pseudo-element :before to insert incremental values:

  ol li
  {
    list-style-type: none;
  }  ol li:before
  {
    content: counter(item) ". ";
    counter-increment: item;
  }

Sharp eyes will have caught one subtle difference in formatting in the screenshot above: the numbers generated with :before are rendered flush-left, whereas browsers typically align ordered list numbering flush-right.  This affects the alignment of the item values when the number of digits changes, as in 9 to 10 above.

How to edit: When items are added to or removed from the overall list, some items will need to be moved from one sub-list to another in XHTML to maintain a consistent column length.  No further change is necessary to either XHTML or CSS to preserve proper numbering.

Unfortunately, this technique will not be practical for any cross-browser purpose until more browsers support the :before pseudo-element.  For now, Opera’s the only browser that can render it.

Numbering split lists with script#section6

While we’re on the subject of content-generation, I’ll add in passing that the items in split lists can also be “physically” numbered contiguously by a script that inserts numbers into the item values, either server-side during page generation or client-side upon page-load.

A server-side script such as ASP or PHP, reading list item values from a database or a flat file, can prepend a consecutive number to each item as it’s written to the page.  It can also divide the total number of list items by the desired number of columns and generate the sub-list markup.  The task of arranging the sub-lists side by side can be left to a static stylesheet.

A client-side script such as JavaScript can locate the sub-lists using ids or classes and iterate through the items, inserting item numbers, suppressing normal ordered list numbering, and splitting one long list into sub-lists as needed.

It should go without saying (but doesn’t) that any solution that uses client-side scripting should gracefully degrade in browsers with scripting turned off.

Wrapping a single list#section7

As we’ve seen, splitting a list into sub-lists and forcing consecutive item numbering can produce the desired cosmetic effect, but those solutions aren’t as satisfying to me as one that preserves the integrity of the single list markup.

To wrap a single list into columns, I use CSS to grab each item that begins a new column and drag it back up to the level of the first item and then over to a new left margin.  Normal rendering does the rest.

Now we’re talkin’ swag!

Here are three ways to mark this up:

Method 4: Wrapping a single list with XHTML#section8

If you’re willing to control where the column-wrap occurs using the XHTML markup, it’s easy enough to mark each item according to which column it belongs to.  An additional class name marks the first item of each column.

Method 4: Wrapping a single list with XHTML

See Example 4.

Here’s an abbreviated example:

  <ol
    ><li class="column1">item</li
    ><li class="column1">item</li    »
    ><li class="column2 reset">item</li
    ><li class="column2">item</li    »
    ><li class="column3 reset">item</li
    ><li class="column3">item</li
  ></ol>

The stylesheet uses these classes to establish the horizontal column positions:

  li.column1 { margin-left: 0em; }
  li.column2 { margin-left: 10em; }
  li.column3 { margin-left: 20em; }

Then we mandate the line-height of each item and bring the first item of each column back up to the level of the first item:

  li
  {
    line-height: 1.2em;
  }  li.reset
  {
    margin-top: -6em;
  }

Vertical return = number of items in a column ∗ height of each item.  In this case, 5 items ∗ 1.2em = 6em.  (When I’ve tried making the line-height smaller than 1.2em I’ve run into cross-browser discrepancies.)

How to edit: When items are added to or removed from the list, the XHTML markup must be tweaked to give items the proper column-classes, and the reset class must be moved to the first item of each column.  When the number of items per column changes, the CSS li.reset{} rule must be changed accordingly.

This isn’t my preferred technique because the column-wrapping is controlled from the XHTML markup rather than the stylesheet.  The way I look at it, wrapping a list into vertical columns is a matter of presentation, not content, and therefore ought to be controlled by the stylesheet in the interests of separating content from presentation.

Wrapping a single list with CSS#section9

In Methods five and six that follow, the points at which the list wraps to a new column are controlled wholly from the stylesheet.

The price we pay is some heavy XHTML markup, gravid with class names.  Check this out:

We prepare the markup by assigning a unique class to each list item:

  <ol
    ><li class="aloe">Aloe</a></li
    ><li class="berg">Bergamot</a></li
    ><li class="cale">Calendula</a></li
    ...
    ><li class="oreg">Oregano</a></li
    ><li class="penn">Pennyroyal</a></li
  ></ol>

(I’m using class instead of id here so that I’ll have the freedom to include more than one wrapped list on the same page; a class may apply to more than one object, but an id must be unique.  The important thing is that each item is uniquely identified within its list.)

In the stylesheet, we assign a different left margin to each group of items that belong in one column:

  li.aloe,
  li.berg,
  li.cale,
  li.dami,
  li.elde
  {
    margin-left: 0em;
  }  li.feve,
  li.ging,
  li.hops,
  li.iris,
  li.juni
  {
    margin-left: 10em;
  }  li.kava,
  li.lave,
  li.marj,
  li.nutm,
  li.oreg,
  li.penn
  {
    margin-left: 20em;
  }

This is reminiscent of the “columnN” classes in Method 4, however here the determination of which item belongs to which column is decided purely on the CSS side of the street.

Oh, the irony of it all!  The prospect of assigning a unique class to each list item is enough to make eyes cross and toes curl.  After all, the reason we’re using ordered lists in the first place is to take advantage of the automatic list numbering that our browsers provide.  If we have to name each list item class uniquely, why not just number the list items themselves and be done with it?

Easy, now.  Breathe.  The assignment of unique item classes isn’t about numbering the items, it’s about the presentation of the whole list—finding ways to persuade the browser to wrap it into columns without actually having to chop the list into pieces and paste them side by side.  In web design we assign classes and ids to page elements routinely to guide CSS presentation; that’s what they’re there for.  Make no mistake, assigning a different class to every item in a list is no one’s idea of elegant code, but it works, it validates, and it doesn’t (in my humble opinion) make the markup intrinsically ‘un-semantic.’

Whether it’s worth your while to manage the code for any of these techniques when you modify a list will depend on how much you want multiple-column lists to work.  Fortunately, it’s not that big a deal.  Editing small lists by hand is easy, and if you’ve got a really long list that’s constantly changing you should probably be generating it from a database in the first place.  One consolation is that when we’re generating pages from a server-side script we can assign unique list item classes automatically so we don’t have to get our fingers dirty.  (Or tired.)

Method 5: Wrapping a single list using absolute positioning#section10

Because we’ve now identified each item uniquely within the list, one possible approach is simply to position each item explicitly.

Method 5: Wrapping a single list using absolute positioning

See Example 5.

Note that this is not a practical cross-browser solution today for ordered lists, since neither Internet Explorer 6 nor Opera 7 will display list markers when list items are styled {position: absolute;}.

To make this work for the rest of the browsers, the entire list must be enclosed within a div with {position: relative}: this gives the absolutely-positioned list items a frame of reference so we can prevent them from simply flying up to the top of the page.  Then it’s simply a matter of assigning vertical positioning to every element.  We can do this in rows:

  li
  {
    position: absolute;
  }  li.aloe, li.feve, li.kava { top: 0.0em; }
  li.berg, li.ging, li.lave { top: 1.2em; }
  li.cale, li.hops, li.marj { top: 2.4em; }
  li.dami, li.iris, li.nutm { top: 3.6em; }
  li.elde, li.juni, li.oreg { top: 4.8em; }
  li.penn                   { top: 6.0em; }

How to edit:  When the number of items per column changes, the stylesheet will need to be changed.  When items are added to or removed from the list, the stylesheet must be edited to re-determine which items reside in which columns.  Every new list item must be assigned a unique class in XHTML.

Absolutely positioning every item in a list is a control-freak’s dream, the sort of approach many of us took when we first came to the web from print design and hadn’t yet learned to let go.  To create multiple-column lists it isn’t necessary to position every item, as Method 6 will demonstrate, but I’m including it here for the sake of completeness.  It does ’break’ differently from Method 6 and that might be one basis for choosing it:

If any list item is long enough to wrap around to a second line, how does that affect the layout of the list?  When list items are absolutely positioned as in Method 5, the overall layout will remain unchanged, but the list item that wraps will be overlaid by the next item in the list which will claim its position without regard to preceding text, since absolute positioning takes each item out of the flow.  In contrast, in Method 6 below each column of list items descends by normal flow; a list item that wraps will push down subsequent items, lengthening the column it’s in.  Because that method assumes a fixed column height, the vertical return will then be insufficient to bring the next column back up to the top, creating a staggered layout.

There may be other ramifications of absolute positioning that will affect our choice: for example, some browsers don’t permit the user to highlight text in absolutely-positioned blocks.

Method 6: Wrapping a single list using normal flow#section11

Finally, here’s the technique I prefer to use: a single semantically-logical list whose column-wrapping is controlled entirely from CSS, relies on normal flow, and works in most modern browsers.

Method 6: Wrapping a single list using normal flow

See Example 6.

As in the previous method, each list item is given a unique class name in XHTML, and the left margin of each column is stipulated in CSS.

What differentiates this method is that here we use those unique item classes to bring the first item of each column back up to the top using a negative margin-top:

  li
  {
    line-height: 1.2em;
  }  li.feve,
  li.kava
  {
    margin-top: -6em;
  }

Again, vertical return = number of items in a column ∗ height of each item.  In this case, 5 items ∗ 1.2em line-height = 6em.

How to edit: when items are added to or removed from the list, the uniqueness of item class names must be maintained in XHTML and the stylesheet must be tweaked to shift items to their proper columns.

Gettin’ fancy#section12

With a little extra styling and some background images, the list can get ready to party without breaking the multi-column flow.

Example 7: Gettin’ fancy

See Example 7.

Where to now?#section13

There are many ways to display a multiple-column list.  As we’ve seen, many of them require a compromise of web standards or browser-compatibility, as well as some pretty hairy markup and styling.  The best choice, in my opinion, is to give the XHTML markup sufficient “hooks” to allow the column-wrapping to be controlled entirely from CSS.

What we really need is for some bright bulb to come along and figure out how to do this with spare markup—and actually claim that holy grail.

Now, go forth and swag!

Browsers & helpers#section14

The examples for this article look substantially the same (except where specifically noted in the text) in Windows browsers Firefox 1.0, Internet Explorer 6, Mozilla 1.7.2, Netscape 7.1 & 6.2, and Opera 7.23, and in Macintosh browsers Firefox 1.0, Internet Explorer 5.2, and Safari 1.0.3.

Less successful with these methods are Windows Internet Explorer 5.x and earlier, Linux Konqueror 3, and Netscape 4.x.  Perhaps with coaxing they could be persuaded to come along as well.

Thanks to Angela Marie, B.J. Novitski, Bruno Fassino, Ingo Chao, Larry Israel, and Zoe M. Gillenwater for their helpful criticism and browser peeks.

Angel Wing photograph in example seven by Sophie Arés Pilon.

About the Author

Paul Novitski

Paul Novitski is a software developer, cat lover, writer, and mbira player who lives in the woods of the West Kootenays. At the best of times he combines his passions for elegant code and progressive change by helping activists and artists with their websites. He has been writing software since 1979.

59 Reader Comments

  1. Some of these methods will get very messy if the text of some of the list items is long enough to wrap lines. Just something to lookout for before committing to a method.

  2. Rather than eliminating all whitespace inside a list element I prefer to just set the

  3. tag to display: inline;

    This may not be suitable in all instances (if you just want a plain list say). However as this list is to be styled further with floats and possibly other display settings (on the links in each li for example) I think it is a more elegant solution.

  4. Could we not maintain semantic integrity, simple CSS, AND get our nice, beautiful, multi-column layout by using Javascript to convert lists into a multi-column variant?

    In this way, everything degrades to a simple list; but for virtually all users, you get your multi-column list with none of the caveats mentioned.

  5. It seems a little funny to use a class to uniquely identify an item.

    Classes tag an item as being part of a group. If the aim is to uniquely identify something on a page, then assigning an ID to the item is probably a better route to go.

  6. David, one issue with declaring list items as inline is that inline elements can’t be assigned dimension in the same way a block element can, limiting their usefulness in creating navigation menus & the like.

  7. Boy, is that ever hairy markup… and absolutely positioning everything?

    I’d rather use multiple lists than have to break DRY like this. I mean, what a disaster when you have to add a nav item in your markup and _then_ track down and fiddle with all these numbers in your stylesheet.

    After all, as stated in the article, it’s like three lines of PHP and an associative array to have complete control. Alternatively, if the markup is sacred, have a tiny server-generated stylesheet that auto-calculates the menu item positions.

  8. Jason, you’re right about the graceful degradability of client-side scripting solutions; while I did mention scripting in passing, I was focusing on CSS solutions in this article. While I use JavaScript myself, it has its own caveats: similarly to CSS it’s not universally supported, neither by makes & models of browser nor by all users. Further, I find that most javscript solutions that purport to leave the markup simple actually heavily augment the DOM when they run. To what extent does it matter whether I complicate the XHTML and CSS by hand or write a script to do it for me if the resulting DOM will be the same?

  9. Method 4 would be the useful when you are generating content using sever side scripting (i.e. PHP), you could have a simple little function that takes a list (i.e. from an array) and splits it into N numbers of columns by assigning class=”columnN” and class=”columnN reset” through a for loop. This removes the need to ‘tweak’ the source each time there’s an item added or removed from the list.

    Only useful for generated content, and probably only in certain situations at that, but once implemented it looks after itself, as long as you can live with presentation vs semantics use of CSS class names.

    Interesting article Paul, thanks for the different ideas.

  10. Like most other readers of articles on this site, I’m always interested in experimentation and exploration of issues related to the implementation of proper standards and markup. And I absolutely enjoyed the exploration of so many different options presented here.

    However, with each new example this article seemed to get farther and farther away from our true “holy grail” of generating pages that utilize the simplest, most compatible markup.

    If all the content were contained within the markup (i.e. not supplied via a CMS), my question is why not use CSS to simply style these lists manually, and avoid using OL & LI tags entirely?

    Certainly the auto-numbering of OL tags is a strong tool and like other aware developers I too long to have numbered columns that can “swagger.” However, if I’m going to build custom code on a list-by-list basis, it seems to make a lot more sense — and is ultimately much more compatible — to do so within the XHTML markup than CSS.

  11. Actually, Konqueror does just fine with the example in Method #3. I would assume Safari does as well. And Firefox handles generated content as well — it’s the counters and counter increments that it doesn’t seem to support. Firefox 1.5 is “supposed to”:http://developer.mozilla.org/en/docs/What%27s_New_in_Deer_Park_Alpha handle counters, but when I load the example they all show up as “1. whatever.” It looks like it’s using the counter, but not incrementing it.

    Come to think of it, IE is pretty much the only major browser that doesn’t support the :before pseudo-element. Unfortunately, last I looked it wasn’t on the “list of additions for IE7”:http://blogs.msdn.com/ie/archive/2005/07/29/445242.aspx

  12. I found a description of what’s different about Firefox 1.5’s implementation of counter-increment. (The release notes just say it matches an upcoming draft of CSS 2.1 rather than the current one, without saying what’s changed.) Apparently you need to put counter-increment “in the tag’s style”:http://www.davidflanagan.com/blog/2005_08.html#000075 instead of in the :before pseudoelement’s style. This still works fine in Opera.

    There is one further step to getting it to work as expected in Firefox, since “scope is an issue”:https://bugzilla.mozilla.org/show_bug.cgi?id=3247#c113 in the new way of handling counters. Ending the LI’s parent — the OL — ends the counter’s scope unless you define it somewhere further up the tree, and you get each column starting over at 1. If you apply “counter-reset: item” to div.outerwrap, it sets the scope so that the counter doesn’t reset on each OL, and you get continuous numbering in both Opera and Firefox 1.5.

  13. Nice in-depth analysis. However, there are some errors in one of your examples. Under “Wrapping a single list with CSS” you have a bunch of orphaned closing anchor tags:

    1. Aloe
    2. Bergamot
    3. Calendula
    4. Oregano
    5. Pennyroyal

    Otherwise, great article!

  14. I recently had to do something similar. I wanted to keep the HTML simple so that the list was easily editable. I relied on a combination of JavaScript and CSS. Sometimes heavy DOM augmentation is necessary to get what you want, but I don’t think it’s necessary for this particular trick.

    My method is similar to #4 in your article, but did a lot of the heavy lifting in JS. One of the benefits is that the JS will determine the height of the list and dynamically create two columns (currently it’s limited to two columns).

    I will note that one problem I encountered that I have not yet had time to fix is the list element marker (number or bullet). IE has a particularly nasty problem where the marker for multi-line list elements shows up next to the last line instead of the first (something to do with “hasLayout” I suppose). Becuase of this the CSS removes the markers, which luckily I did not need.

    The code still requires quite a bit of clean-up, I still have some improvements to make, there are some problems that I have yet to figure out, and the documentation is pretty much non-existent. Anyone interested can find a demo page at < http://www.eclecticgeek.com/mcl.asp>.

  15. Any easy way to force IE to render without list-items without whitespace is to just define a line-height. I use:

    li {
    line-height: 150%;
    }

    This styles the spacing to match my paragraphs, and seems to get IE to play nice with the other browsers. It sure beats having to do line-breaks in your HTML. 😉

  16. Tweak this so that instead of having a unique class (.name) for each value, just have a class that indicates what column it should sort into…

    .col1
    .col2
    .col3
    and so on, and then just toggle through those when adding to your list. You could automate this even further by running this through a server for the numbering party (and even for defining which class each

  17. would be, as the author suggests.

    Let me know if this is tomfoolery.

  18. Jason, the fundamental justification to use OL & UL structures for lists rather than Ps or DIVs is to give our documents semantic meaning. When the content we present is meaningfully structured and not merely visually presented, our pages can more readily be interpreted by screen-readers, search engines, archiving engines, and other systems.

  19. Yasir, the purpose of making the normally inline A element a block is to enable us to assign it dimension. When an anchor is a block, its clickable area is the full area of the block, whereas with inline anchors you can click only the hyperlinked text (or image) itself.

    You can see this effect in all of my live examples but especially “example 7”:http://alistapart.com/d/multicolumnlists/example7.html when you mouse over any portion of the rectangle that contains a list item.

    I like to do this to increase usability (the user doesn’t have to aim as precisely to activate a link) and attractiveness (in menus I often like to change the entire menu item block background on hover, not merely the link text itself).

  20. As “Kelson Vibber”:http://www.alistapart.com/comments/multicolumnlists?page=2#13 noted above, Firefox 1.5 beta can be made to work with Example 3. One just have to use the correct syntax. Another browser that supports this perfectly is little brother iCab 3.0. Safari (1.3 and 2.0) isn’t there yet.

    One problem to note with those various techniques (i.e. example 3): the second and third column are not perfectly aligned horizontally. Each column starts a few pixels higher or lower than the previous one. That is a consequence of the way browser round of values to the nearest pixel. On the Linux box, Firefox is correct, but Opera 8.5 was off by a few pixels. On my OS X box, the only browser to get it correctly is Firefox 1.5b.

  21. Thanks everyone who suggests that server- or client-side scripting is the solution to the multiple-column list problem. Of course, any of the HTML & CSS solutions I’ve suggested in this article can be the underlying method used by a script. No reason to do it by hand if you can program a robot to do it for you.

  22. Paul, there’s no argument from me when it comes to the importance of using OL/UL for the purposes of defining structure and the nature of the content. And don’t get me wrong — I love this article and I’m not trying to criticize you or its content in any way. I’m just looking to further the discussion on this subject, because I think you’ve brought up a significant conern.

    Hopefully I can make myself a bit more clear in my thoughts. 🙂

    Wrapping OLs across multiple columns is only a “holy grail” of the *visual* presentation aspects of XHTML/CSS. Yet this barely registers on the radar if we’re talking about presenting content to a screen reader.

    More importantly, as visually pleasing as multiple-column OL may be, the truth is that when presented on-screen, it tests miserably with reagards to usability (according to Edward Tufte and NN/G). This is not to say we should abandon our little Grail quest in this regard. On the contrary, I think this brings into question how important it is that certain content be presented in numerical order.

    While this article is presented from a technical/development standpoint, but isn’t the underlying message of ALA that development and design need to go hand in hand?

    We all love CSS and we all dream of heightened standards for both development and display. But our hopes for the future shouldn’t derail us from our primary responsibility as developers, and that is delivering universally readable content that maintains visual presentation.

  23. Another potential methodology is to not create the content in xhtml and stick to xml and use a xslt to style things. This allows a programatically neutral backend that can be reproduced on many levels. It also lends it self to transformation to other formatting that can be machine readable (RDF or otherwise). The negative of course is it does require scripting of some sort.

    A larger is issue to consider is that it brings forth whether or not semantic markup really matters. It is no where as close to being as usable or parseable as something like RDF or some other user created xml format. I am not saying of course that we should use xml documents to return to tons of tables and the like, but rather that our consideration for what is really “semantic” or proper should be considered in context. If an application drives this example, then it seems to be a moot point IMHO. Of course, if someone had to edit the pages (be it javascript, xhtml, css or otherwise) the strategies become much more important.

    Thanks for the interesting article.

  24. Thanks, Philippe, for reports of vertical alignment problems in Linus & OS X browsers. In my early experiments with these techniques, I found that I had to set the line-height of list items to at least 1.2em in order for methods 5 & 6 to bring the first item of the next column up to the correct position. I’m surprised that you report a similar problem with method 3, which is just three separate lists floated side-by-side. I’ll look into it.

  25. Jason, it sounds as though you and I agree on this point: that wrapping lists into multiple columns is a matter of visual presentation and shouldn’t affect the semantic content of the markup. That’s why I prefer methods that employ a single list manipulated by CSS over those that fragment a list into fragments positioned side by side (regardless of whether that fragmentation is performed manually or with script).

    I don’t see why a single list, wrapped visually into columns using CSS, would present problems for screen readers; can you elucidate? Also I’d like to read the Edward Tufte article you cite; can you send me a link or bibliographic reference?

  26. Anissa, your suggestion for assigning class names to list items in markup in order to place items into columns sounds like what I described in “Method 4”:http://alistapart.com/articles/multicolumnlists/#method4 — let me know if you had a different idea in mind.

    I don’t think it’s tomfoolery — it’s certainly workable and probably accessible to screen readers. In my opinion it’s not an ideal solution since it codes the column placement in the content (HTML) rather than in the presentation (CSS), and I consider column-wrapping presentational.

  27. Very interesting article. Until the CSS 3 columns module gains wide support, I still prefer to use Method 1; I think I first saw it used on Eric Meyer’s site a year or two ago. I like the simplicity of the XHTML and CSS with no extra scripting. Actually I was surprised how quick and easy it is to recognize that the sequence is running horizontally and make the visual adjustment (using numeric markers might be a different story, though). I haven’t seen any of the usability data sited earlier that suggests problems with this approach (anyone willing to site a URL?). And I haven’t used this method with item markers, so that hasn’t been a problem for me either.

  28. Paul,
    Isn’t that just the funniest thing! I was so excited because I had actually thought of something to post on ALA. I was too tired to realize it was your idea! My head is hung in shame.

    Oh well, I shall console myself with the idea that at least I’m *thinking* like an ALAer. Sigh.

  29. Paul (re: “28”:http://www.alistapart.com/comments/multicolumnlists?page=3#28 ). Actually, I was wrong in pointing to example 3; that one works correctly, as far as vertical alignment is concerned. The problem applies to those examples where you use negative top margins. When you first published your experiments on CSS-D, I did some testing on this. Never could them right across browser land, except when using either pixels for values, or using a line-height like ‘1’ or ‘2’. It is all due to way browsers attempt to slice pixels or rather the way they convert to screenpixels to render on screen when the line box does not have a round number of pixels for computed height.

  30. Philippe (re “33”:http://alistapart.com/comments/multicolumnlists?page=4#33 ), can you tell me if you preserved my markup in its entirety — including the Strict DOCTYPE — when you tested examples in Mac browsers? In my early experiments I found more default LI height variance between browsers with transitional or quirksmode rendering, confounding the vertical return calculation, which is one reason I ended up using Strict for this article.

  31. in example 6 you use different “unique” li classes to set up the margin’s. Is there really a need for unique handlers? I don’t get it. I guess in this case are 3 class-names enough.

    However, nice idea:)

  32. This may be incredibly pedantic but I thought an ‘ex’ was the measure of font height whereas an em is the measure of font width.

    But I found the article and subsequent comments very intersting and learnt something, so thanks.

  33. Thanks for a nice article, even though I don’t think I will be using the techniques. I prefer the (unobtrusive) javascript approach, with the ol or ul having a class “multiplelist” or whatever. The possible problem with multiple-line / different height-li’s seems to be catered for with a little help from the linked source in Brian Sweeney’s comment (#16).

    I can see a real potential use of multiple column lists in e.g. feature lists on product information pages.

  34. Eike (re: “36”:http://alistapart.com/comments/multicolumnlists?page=4#36 ), the purpose of assigning each list item a unique identifier within its list is to enable us to control column-wrapping entirely from the stylesheet.

    Both methods 5 & 6 use “this technique”:http://alistapart.com/articles/multicolumnlists/#method56

    If we use only one class name per column as in my “method 4”:http://alistapart.com/articles/multicolumnlists/#method4 then column-wrapping is controlled in large part from HTML. In the interests of separating content from presentation, I prefer to control the wrap from CSS.

  35. Pär (re: “38”:http://alistapart.com/comments/multicolumnlists?page=4#38 ), thanks for your comments. Even if you use javascript, your script needs to use some technique or other to wrap the list into columns. If you use a technique other than one of the ones I describe here, please describe it for us to add to the heap.

    I don’t see javascript as an alternative to CSS, I see it as an alternative to hand-coding. Whether or not you build a robot to do the dirty work, the work still has to be done by a specific technique. Which do you choose?

  36. *Re: #41*
    Paul! I’m sorry, I was probably too quick with my comment. It’s true, I would still have to choose the way to do the actual column wrapping.

    My apologies if the rest of this post is not understandable due to my language. English isn’t my mother tongue. Please correct me or ask if anything seems unclear.

    The technique I choose would have to fulfill at least the following: it has to be degradable (“unobtrusive”), not use deprecated elements or attributes, and *not force me to manually change the js, css or (x)html* when adding or removing items to/from the list (except the manual change of adding or removing the items, of course). This removes method #2 from the list. I would use method #1 if I only used unordered lists (giving the ul a class of “multipleUnorderdList” and styling accordingly). If :before and content properties where widely supported, I would probably prefer method #3 (with the “sublists” created by js), and accept the extra list elements even though they might be unsemantic. My critique against method #5 and #6 is mainly because I would have to change the css (add classnames) on every change on the list (also, the use of classes on every element looks ugly! :).

    So I was thinking about method #4, and accept the “presentation intrusion on the content”. The columnN and reset classes would be set by js, making it easy to add or remove items. The ol would get a class “multipleOrderedList” or something similar for the js to “see” and work upon. I would still have to update the css when adding/removing items though, so I have to throw this solution away, too. This leaves me with nothing. If being forced by a customer to make a multicolumn ol, I would use something like method #6 and be angry about the solution not being dynamic. I would consider the problem unsolvable on my criterias until I saw “:before” and the “content-property” being widely supported, or read another ALA-article telling me how to do it. 🙂

    Still, this was a nice article. It got me thinking. Thanks. And if someone could point me to a solution that fulfills my criterias, thanks again!

  37. Uhu. The pseudoclass “:before” should be after the word saw in the long link in my previous post (#42). It was parsed as a textile command for “link”. I can’t see how I could miss that in the preview (which btw is a great thing!). I’m sorry.

  38. Pär (re: “42”:http://www.alistapart.com/comments/multicolumnlists?page=5#42 ), thanks for your thoughtful response (in excellent English!).

    JavaScript can automate all of these methods, even 5 & 6 if you let go of the separate stylesheet and accept some form of inline styling.

    Separating presentation in a stylesheet from content & structure in an HTML file is sensible when creating and maintaining web projects. However, when a browser actually renders a page it necessarily mixes the two into a single DOM. If you think of a javascript function as part of that rendering process, then the fact that it mixes CSS with HTML might be deemed forgiveable.

    To automate methods 4, 5, or 6, javascript can generate unique classes or ids and apply them to the list elements, then create CSS rules to arrange the list items into columns. While javascript can’t literally modify a linked stylesheet file, it can modify the HTML document itself: insert inline styles into tags (ugh), insert a stylesheet into the HTML header (better), or modify the stylesheet in the DOM (best but more tricky and perhaps less cross-browser-friendly).

    Does it really matter if imposing classes on all the list items “looks ugly” when you the human will never see it? Or doth the mere idea of doing so offend thine eye?

    While method 6 may be my favorite when coding by hand, if I were to assign javascript the task of formatting a list into columns, I think method 4 would require less scripting logic and less new material added to the DOM.

    Warm regards,
    Paul

  39. Paul, thanks for your explanation. But I still don’t think any of the methods will work for me (please correct me if I’m wrong!). I can see what you mean with inline styles “on-the-fly” being acceptable for the sake of function, but I definitely wouldn’t like to mix css into either the html/content file or the js-file. That would make it too hard for a designer to change the styles. Restyling the site should, in my opinion, only be a matter of replacing one or a few css files for some other. I think the only way the script should be able to modify styles is by assigning class names and ids to the elements. Then it’s up to the designer to style for it.

    But you already know that. It’s me not understanding that we were looking at two different things. You wrote an article about different (experimental) ways to handle a problem. I was looking for the perfect solution, to use in a “write once, use anywhere”-script that could automate the process for me while being modular. Your article was great, I just have to accept that it didn’t provide the solution for me. It still was some interesting reading, and your answer to my comment and questions did clear a few things up. Thanks again!

    About individual classes on the list items: Yea, it’s true I would never have to see it if I assigned them from a script. It would offend me knowing they were there 😉 but I could probably accept the usage. Still, the problem of having to update the css file manually matters more to me. As I surely have said to many times now. 🙂

    Looking forward to see your next article, hopefully on ALA.

    Regards, Pär.

  40. I read this article with pleasure & pain. I don’t get it why I should declare every item an own stylesheet just to get it in columns when I can do it simple with boxes?!
    Whatever, I guess the only possibility is to enact the Firefox 1.5 to every user until we got CSS3 as a standard 😉

    It would solve all problems with dynamic cols.

    (to be honest, every browser should integrate the column-css that we can create appropriately layouts!!)

  41. It would seem to me that this is a clear case where Tables should be used, at least for now.

    Yes, I’ve burnt the midnight oil creating entirely table free layouts using CSS2, but sometimes I can’t help wondering if I’m being too much of a purist, or rather, a prophet.

    In a world where we constantly innovate, we can often forget the simplicity of older innovations. This can result in brilliant ideas, but more often than not, turns into an overly complex “because I can” hack.

    The simplicity of multi-column layouts in standard HTML is so profoundly accessible, I see no point in trying to complicate the issue. It is, in essense, tabular data.

    The basic table tags are accessible by the vast majority of browsers and browsing devices and can be augmented with CSS2.

    Perhaps I’m missing the point entirely ?

    Although I will attempt these various CSS2 hacks, the humble HTML table remains to me, a vision of simplicity and elegance.

  42. Mat (re: “47”:http://alistapart.com/comments/multicolumnlists?page=5#47 ), tables might seem to be a solution at first, but let’s think it through. For a three-column table, are you proposing a table with 3 columns and N rows? Table cells are read across and then down, so if your table consisted of these nine cells:

    a | d | g

    b | e | h

    c | f | i

    …then your data sequence in the markup would be a,d,g,b,e,h,c,f,i. Put that in your screen-reader and smoke it! I think that’s sacrificing the integrity of the data in order to use table markup to get things to align properly.

    Another way to use a table for list-wrapping is to create one cell per column, so cell 1 would contain the first part of the list:

    a

    b

    c

    cell 2 would contain the next part:

    d

    e

    f
    and so on. This splits the list into several sub-lists, which falls short of the ideal single semantic list, and uses the table purely for layout and not for its semantic structure.

    For me, another philosophical & practical disadvantage of using tables is that the markup controls the column-wrapping, which I consider presentational.

    Please let me know if I’m overlooking a table-based solution that preserves the sequence of the original list and uses the table as a semantically-meaningful structure and not merely a visual layout tool.

  43. If we’re talking which is more Semantically Pure™, I’m really not convinced that an attribute-encrusted nightmare of IDs beats a two-celled table. Never mind, fer-God’s-sakes, unique _classes_. Yeah, they’re attributes and not structure. They also blow out the character count sonmething fierce.

    That said, I probably wouldn’t be keen on splitting an _ordered_ list into columns of any sort anyway, what with it being a colossal pain in the bum to read. If I do, however? Ara’s JavaScript example looks pretty good right now.

  44. I used multi-column method for my list but i have a problem:
    my list have many rows and when i print it, it is printed only in one page and is not divided in multi-pages and i lost the last rows.

  45. I’ve been messing with the CSS and HTML for awhile, but I can’t figure out how to get text to show up like:

    Text 1 Text 2
    Text 3
    Text 4

    and then repeat. Any help would be nice. (anormlguy007@yahoo.com)

  46. On my previous one, the spacing didn’t go through…

    Text 1 aligned left, then Text 2, 3, and 4 aligned right, with Text 1 and 2 on the same line.

  47. I used to design with HTML tables but then I found out the drawbacks of table and use of Divs instead. However my Float Left divs wrap underneath one another when they dont fit in the borwser’s size, for eg
    [first div] [second div] [third div]
    if you customize the size of browse to less 800 i get something like:
    [first div] /* second wraps */
    [second div] /* third wraps */
    [third div]

    Any idea as to How I need to solve this issue? Thanks.

  48. Nice post, one that I’ve only just stumbled upon….so I’ve read it, and im pretty sure that my navigation isn’t going to change, so I’ve opted for Method 4.

    And it works…that is until I changed it to use a bulleted list and instead of using a bullet image, I used a background image. Adding the code below to the “ul li” class works for all but the top two items in each subsequent list after the first. Removing the “position:relative” ie/hack solves it, but then the ol’ link problem reoccurs..

    background-image: url(../images/bullet.gif);
    background-repeat: no-repeat;
    background-position: 10px;
    padding-left:30px;

    Any ideas how I can get round this?

  49. I used method 6 for a 2-column list but noticed that in IE the second column inherited the background from the parent element. It has something to do with the negative margins as using a positive or zero margin resulted in no background for the second column. The easiest way around this bug is to set the background to “none” for all li elements.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

Nothing Fails Like Success

Our own @zeldman paints the complicated catch-22 that our free, democratized web has with our money-making capitalist roots. As creators, how do we untangle this web? #LetsFixThis