How can I break a single list into multiple columns semantically? [duplicate]
Asked Answered
A

5

8

The current approach our site is using to break a single list into multiple columns is to use multiple uls:

<ul>
  <li>one</li>
  <li>two</li>
</ul>
<ul>
  <li>three</li>
  <li>four</li>
</ul>

This doesn't seem ideal to me, since semantically, it's not two lists, it's one. I've seen a lot of inadequate solutions for multi-columns lists. I'm looking for a solution that:

  1. Uses the following markup structure

    <ul>
      <li>one</li>
      <li>two</li>
      <li>three</li>
      <li>four</li>
    </ul>
    
  2. Organizes like this:

    one    three
    two    four
    

    Not this:

    one    two
    three  four
    
  3. Works without modification if list-length changes, or if items wrap multiple lines.

  4. Uses valid, semantic HTML.
  5. Works down to IE8.

The ideal solution is CSS only, but I'm open to using jQuery UI or light-weight Javascript.

(PS, here's my CSS attempt, with notable problems.)

Edit: This question is specific to semantic lists. The other supposedly "duplicate" question is regarding <div> elements, which is a different ballgame because additional markup is allowed. No additional markup is allowed between a <ul> and an <li>. This question is focused on semantics which means there is an emphasis on using HTML to indicate with as much detail as possible the meaning of the content.

Adonic answered 15/8, 2013 at 20:11 Comment(6)
CSS only would be difficult as you'll need to know which ones to float left and which ones to float right, however with js all you'd have to do is add one class to the first half of them and another class to the second half, one floating left the other floating right.Characterization
you can use nth child selectors like odd and even, but it wouldn't work in IE8Bettis
@BrianGlaz odd/even would result in one two on row one and three four on row two rather than the intended one three and two fourCharacterization
Honestly, @cimmanon DIVs are a whole different ballgame. You don't have the semantic limitations that you have in lists. Multi-column lists are a specific issue, in fact, alistapart has an entire article on the topic.Adonic
@Adonic No, there's no difference between a ul and a div when it comes to any solution (CSS or JavaScript). If you want IE8 support, you've effectively eliminated all pure CSS options.Tuantuareg
@Tuantuareg With DIVs, there are some fairly simple solutions involving adding extra markup. Doing that within a list would create invalid HTML.Adonic
A
3

Sorry to answer my own question, but I think I got a CSS only solution.

Here it is on jsFiddle.

Basically, I use the general sibling selector (This symbol: ~, works in IE7 too!) to highlight all elements after the breakpoint, and move them over and up. As long as you set a line-height, seems to work cross browser down to IE7.

HTML:

<ul>
  <li>one</li>
  <li>two</li>
  <li class="newline">three</li>
  <li>four</li>
</ul>

CSS:

.newrow,
.newrow ~ li {
    margin-left: 120px;
    margin-top: -100px;
    margin-bottom: 100px;
}

Am I missing anything obvious here?

Adonic answered 15/8, 2013 at 21:5 Comment(5)
Hi, thanks for sharing this, it's a cool idea. Question - how to do three columns?Falco
Just do class=col2 and class=col3 where you want the column breaks to start. Then make the CSS same as what I have but for col3 it's margin-left: 240px.Adonic
Here's a JSfiddle to explain better. Basically .col2,.col2~li{margin-left:270px; margin-top:-220px; margin-bottom:220px;}Adonic
Hi, I commented out the indentation rules, and now there is an extra line at the top of column three - any way to avoid this? Thanks, I appreciate the helpFalco
Ok, I put some different data in there and the "problem" seems to have gone away; maybe you know the reason this happened though – I don't know what happenedFalco
S
6

You could use columns for browsers that support it

DEMO http://jsfiddle.net/kevinPHPkevin/eaewX/33/

Here is a jQuery example for full browser support

DEMO http://jsfiddle.net/kevinPHPkevin/MKL4g/131/

var postsArr = new Array(),
    $postsList = $('ul.posts');

//Create array of all posts in lists
$postsList.find('li').each(function(){
    postsArr.push($(this).html());
})

//Split the array at this point. The original array is altered.
var firstList = postsArr.splice(0, Math.round(postsArr.length / 2)),
    secondList = postsArr,
    ListHTML = '';

function createHTML(list){
    ListHTML = '';
    for (var i = 0; i < list.length; i++) {
        ListHTML += '<li>' + list[i] + '</li>'
    };
}

//Generate HTML for first list
createHTML(firstList);
$postsList.html(ListHTML);

//Generate HTML for second list
createHTML(secondList);
//Create new list after original one
$postsList.after('<ul class="posts"></ul>').next().html(ListHTML);
Sanderson answered 15/8, 2013 at 20:19 Comment(2)
great jQuery approach. Instead of trying to figure out what to divide by, the JavaScript does it for you. plus everything falls back to a single list if JavaScript is disabled, and it folds if the window is thin. - thanks for sharing!Automate
Hi Vector, I will be using your code and modifying it to achieve a similar task. Thank you for posting. I am fuzzy on the legalities and such so I will also link to this answer in my code. Please let me know if you have any objections.Gwendolyn
D
3

The "correct" way to do this would be to use CSS3 columns:

But that will only get you to IE10.

I would recommend going that route and then using IE shims and javascript o handle IE9 and IE8. I forget, but modernizr might provide a shim for helping you out.

Downwash answered 15/8, 2013 at 20:19 Comment(1)
Where's the code? If those links go down, this answer will be useless to future users.Tuantuareg
A
3

Sorry to answer my own question, but I think I got a CSS only solution.

Here it is on jsFiddle.

Basically, I use the general sibling selector (This symbol: ~, works in IE7 too!) to highlight all elements after the breakpoint, and move them over and up. As long as you set a line-height, seems to work cross browser down to IE7.

HTML:

<ul>
  <li>one</li>
  <li>two</li>
  <li class="newline">three</li>
  <li>four</li>
</ul>

CSS:

.newrow,
.newrow ~ li {
    margin-left: 120px;
    margin-top: -100px;
    margin-bottom: 100px;
}

Am I missing anything obvious here?

Adonic answered 15/8, 2013 at 21:5 Comment(5)
Hi, thanks for sharing this, it's a cool idea. Question - how to do three columns?Falco
Just do class=col2 and class=col3 where you want the column breaks to start. Then make the CSS same as what I have but for col3 it's margin-left: 240px.Adonic
Here's a JSfiddle to explain better. Basically .col2,.col2~li{margin-left:270px; margin-top:-220px; margin-bottom:220px;}Adonic
Hi, I commented out the indentation rules, and now there is an extra line at the top of column three - any way to avoid this? Thanks, I appreciate the helpFalco
Ok, I put some different data in there and the "problem" seems to have gone away; maybe you know the reason this happened though – I don't know what happenedFalco
C
2

CSS Columns is the best solution but doesn't work for older browsers. You could use nth-child to do something close, but you wont get a split list (you also need a polyfill to use it in older browsers). The easiest solution would probably be a javascript one - it will split the lists but also leave your code clean. Here is a function for turning lists into any number of columns.

http://jsfiddle.net/UrH69/

SCRIPT

$.fn.extend({
    list2Columns: function(numCols) {
        var listItems = $(this).find('li'); /* get the list data */
        var listHeader = $(this);
        var numListItems = listItems.length;
        var numItemsPerCol = Math.ceil(numListItems / numCols); /* divide by the number of columns requires */
        var currentColNum = 1, currentItemNumber = 1, returnHtml = '', i = 0;

        /* append the columns */
        for (i=1;i<=numCols;i++) {
            $(this).parent().append('<ul class="column list-column-' + i + '"></ul>');
        }

        /* append the items to the columns */
        $.each(listItems, function (i, v)
        {    
            if (currentItemNumber <= numItemsPerCol){
                currentItemNumber ++;
            } else {
                currentItemNumber = 1;
                currentColNum ++;
            }
            $('.list-column-'+currentColNum).append(v);
        });
        $(this).remove();
    }
});

USAGE

$('ul').list2Columns(2); // Change this number to change num of columns

HTML

<ul>
  <li>one</li>
  <li>two</li>
  <li>three</li>
  <li>four</li>
  <li>five</li>
  <li>six</li>
</ul>

CSS

.column { float:left; }
Carduaceous answered 15/8, 2013 at 20:18 Comment(2)
You've got a good start, but it's not finished. This code will fail, eg., if you have multiple lists on one page.Capable
True, but if you give the list an ID and target it directly that will solve the problem.Carduaceous
G
1

you can use css column-count for that this demo works in chrome only but you can make it cross browser

http://jsfiddle.net/UuwKC/

ul {-webkit-column-count:2}
Gurrola answered 15/8, 2013 at 20:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.