Alex Angas
Alex Angas

Reputation: 60037

How to CSS a two column list of items?

I need to display a two column list of items according to the following rules:

Here is an example:

~ Item 1   | ~ Item 6
~ Item 2   | ~ Item 7
~ Item 3   | ~ Item 8
~ Item 4   | ~ Item 9
~ Item 5   |

The HTML can be anything as long as it solves this problem. I'm restricted to using XSLT to wrap HTML around what the server spits out. I have access to two XSLT parameters: one that tells me the current item number and one that tells me how many items there are.

My CSS skills are basic/intermediate and I don't know where to start here. Any ideas on whether this is achievable and how to do it?

Update:

Thanks for the answers. Consensus seems to be either use the A List Apart article or a table which I'd prefer as it's simpler. The problem with the table is that the server gives me the items in sorted order. To use a table would mean XSLT trickery to re-sort, wouldn't it?

<tr>
    <td>Item 1</td>
    <td>Item 4</td>
</tr>
<tr>
    <td>Item 2</td>
    <td>Item 5</td>
</tr>
<tr>
    <td>Item 3</td>
    <td>&nbsp;</td>
</tr>

Upvotes: 15

Views: 33341

Answers (7)

guymac
guymac

Reputation: 406

You just need the CSS multi-column properties, e.g. ul { column-count: 2; }.

See Can I Use for support information on CSS Columns.

Upvotes: 11

newUserNameHere
newUserNameHere

Reputation: 18001

The problem with a-list-apart, from what I could see, is that if you split a list of 11 items into 2 colums, I would want the 6th item to be in the first list. alistapart I think puts the 11th item instead in the second column.

My fix was to use jQuery-Columns Plugin. For example to split a ul with a class of .mylist into 2 columns you would do

$(document).ready( function () {
  $('.mylist').cols(5);
});

Here's a live example on jsfiddle

Upvotes: 0

Danimal
Danimal

Reputation: 1228

You could try something like:

.option:nth-of-type(n+5) {
    position: relative;
    left: 14ex;
    top: -6em;
}

.option:nth-of-type(n+9) {
    color: red;
    left: 28ex;
    top: -12em;
}

... for four items a column, where line-height is 1.5em and column width will be 14ex.

But because this uses relative positioning, you'll probably have to reduce the size of the container.

Also, according to w3schools, this property isn't available in IE8 or lower.

Upvotes: 3

Tomalak
Tomalak

Reputation: 338316

I know that people dismiss HTML table based layouts, but what the heck. They work. If you are vain, you are free to go the extra mile and find a pure CSS based way to do it. :-)

So here goes an XSLT solution.

<xml>
  <item id="1">Item 1</item>
  <item id="2">Item 2</item>
  <item id="3">Item 3</item>
  <item id="4">Item 4</item>
  <item id="5">Item 5</item>
  <item id="6">Item 6</item>
  <item id="7">Item 7</item>
  <item id="8">Item 8</item>
  <item id="9">Item 9</item>
</xml>

With this XSL 1.0 template applied (the number of columns is even configurable):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html" indent="yes" omit-xml-declaration="yes" />

  <xsl:template match="/xml">
    <xsl:variable name="vCols"  select="2" />
    <xsl:variable name="vCount" select="count(item)" />
    <xsl:variable name="vRows"  select="ceiling($vCount div $vCols)" />
    <xsl:variable name="vIterC" select="item[position() &lt;= $vCols]" />
    <xsl:variable name="vIterR" select="item[position() &lt;= $vRows]" />
    <xsl:variable name="vSelf"  select="." />

    <table>
      <xsl:for-each select="$vIterR">
        <xsl:variable name="vRowIdx" select="position()" />
        <tr>
          <xsl:for-each select="$vIterC">
            <xsl:variable name="vOffset" select="$vRows * (position() - 1)" />
            <td>
              <xsl:value-of select="$vSelf/item[$vRowIdx + $vOffset]" />
            </td>
          </xsl:for-each>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>

</xsl:stylesheet>

Yields:

<table>
  <tr>
    <td>Item 1</td>
    <td>Item 6</td>
  </tr>
  <tr>
    <td>Item 2</td>
    <td>Item 7</td>
  </tr>
  <tr>
    <td>Item 3</td>
    <td>Item 8</td>
  </tr>
  <tr>
    <td>Item 4</td>
    <td>Item 9</td>
  </tr>
  <tr>
    <td>Item 5</td>
    <td></td>
  </tr>
</table>

In case there is only one item, it produces:

<table>
  <tr>
    <td>Item 1</td>
  </tr>
</table>

So no two columns in this situation.

In any case the table will always be well-formed (e.g. no jagged rows). The one thing that this solution does not do is sorting the output. The output will always be in document order. You seem to get the items sorted properly, so this should not be a big problem.

Upvotes: 3

pinxi
pinxi

Reputation: 543

If you can divide the list using little javascript (which I am not good at), then this should work:

<script type="text/javascript">
var i=0;
var n=4;
if (i==0)
  {
document.write("<div id='listFlow'><ul>");
}
for (i=1;i<=15;i++)
{
if (i==n)
  {
document.write("<li>The number is " + i + "</li>" +"</ul><ul>");
var n=n+4;
  continue;
  }
document.write("<li>The number is " + i + "</li>");
}
document.write("</ul></div>");
</script>

<style type="text/css">
#listFlow ul{
float:left;
width:200px;
margin:0 0 0 10px;
padding:0;
}

#listFlow ul li{
list-style-position:inside;
list-style-type:disc;
margin:0;
padding:0;
width:180px;
}
</style>

Note: Please look at the CSS and NOT the javascript. This is just to show that how the lists can flow as described.

Upvotes: 0

cherrypj
cherrypj

Reputation: 204

One of your criteria is reading the list down, so a simple solution is a two-column table (@Aaron Digulla), with the first half of your list in the first column, etc. You can use any HTML, right? We've resorted to this method for some of our lists, since we didn't want to edit our CSS every time a list changed (nor did we want to remember to edit the classes in the HTML).

Method 1 in the ALA article would be the 2nd simplest solution (and more ideal from a purist point of view). Since you know the current item number and how many items there are, you could add some logic to get list items in the correct order.

CSS can easily style either method.

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328724

CSS can't do this. You need to collect the list on the server and distribute the items in two table columns (you can use CSS to make the two columns the same width).

Note: There are browser extensions for multi column layout but they are not portable.

[EDIT] I'm aware of the article on alistapart but I can't see a difference to my solution: The proposed multi column layout gives each item a fixed position (by giving each item a unique CSS ID and then positioning it somehow). This means you need to generate the HTML and sort the items on the server and then use lots of tricks to position them.

It's much more simple to use a table with two columns and drop the items into them during rendering.

Upvotes: 1

Related Questions