tags:

views:

1260

answers:

11
+3  Q: 

CSS: two columns

I can't figure out how to achieve the following layout with CSS (probably because I don't actually know CSS).

I have a bunch of divs like this:

<div class="right"> <p>1</p> </div>
<div class="left">  <p>2</p> </div>
<div class="left">  <p>3</p> </div>
<div class="left">  <p>4</p> </div>
<div class="right"> <p>5</p> </div>
<div class="right"> <p>6</p> </div>

(not the real contents)

Now I want the layout to look like two equal columns of divs, with the "right" ones on the right, and the "left" ones on the left, thus:

2 1
3 5
4 6

[Edit: In a previous version of this question I had textareas inside the divs, and the divs all had different names like "one" and "xyz".] I tried something like

div.right { width:50%; float:right; clear:right; }
div.left { width:50%; float:left; clear:left;}

but it doesn't quite work: It produces something like:

2 1
3 
4 5
  6

(without the "clear"s, it blithely produces

2 1
3 4
6 5

which is not what is wanted).

It is apparent that it can be made to work if the divs are ordered differently, but I'd like not to do that (because these divs are generated dynamically if the browser has Javascript, and I don't want to change the actual order that is displayed in the absence of Javascript, for semantic reasons). Is it still possible to achieve the layout I want?

[For what it's worth, I'm willing to have it not work on IE or older versions of other browsers, so if there is a solution that works only on standards-compliant browsers, that's okay :-)]

A: 

If you get rid of the clears it works fine. It looks like there's some overlap in the middle for whatever reason (rounding error?).

Ant P.
No, it doesn't work without the "clear"s -- it produces something quite different.
ShreevatsaR
+2  A: 

You try to separate your input stream into two independent streams, and I don't think CSS allows you to do it. Using left and right floats is a clever idea, but it will not always work. CSS spec says in 9.5.1 rule 5:

The outer top of a floating box may not be higher than the outer top of any block or floated box generated by an element earlier in the source document.

So, your floats can jump left, right and down, but not up.

You can achive everything with absolute positioning, but there is nothing automatic about it.

EDIT: I said in comments that I don't expect CSS3 to support a feature like that because it violates separation of content and layout. Later, I remembered that CSS3 plans to have footnotes. So, if you mark some of your divs with "float: note", the selected divs will flow into a separate, "footnote" area of the page. I guess you can place them on the right of the rest of the layout.

buti-oxa
Good description of my problem, thanks. Is there something in CSS3 maybe?
ShreevatsaR
In css (not just css3), you can do things like "display: table-cell;" That would give you exactly what you want.
dave mankoff
Not as far as I know. It may be argued that what you want changes structure/content of the document and thus is beyong the mandate of CSS which is supposed to only change presentation.
buti-oxa
+1  A: 

The following works for me in Firefox 3:

<html>
    <head>
         <style>
            div {width: 198px; border: 1px solid black; }
            div.onediv, div.tendiv, div.xyzdiv { float: right;}
            div.twodiv, div.abcdiv, div.pqrdiv { float: left;}
        </style>
    </head>
    <body>
        <div style="width: 400px;">
            <div class="onediv"> <p>One name</p> <textarea id="one"></textarea></div>
            <div class="twodiv"> <p>Two name</p> <textarea id="two"></textarea></div>
            <div class="tendiv"> <p>Ten name</p> <textarea id="ten"></textarea></div>
            <div class="abcdiv"> <p>Abc name</p> <textarea id="abc"></textarea></div>
            <div class="xyzdiv"> <p>Xyz name</p> <textarea id="xyz"></textarea></div>
            <div class="pqrdiv"> <p>Pqr name</p> <textarea id="pqr"></textarea></div>
        </div>
    </body>
</html>

Just a quick note: putting "div" inside your class name (i.e. abcdiv) is a little redundant and weird. Without going into a huge diatribe about proper class usage, I would leave it out.

You can actually simplify your code if you just say "col1" and "col2". That way you can even swap the columns later if you want:

<html>
    <head>
         <style>
            div {width: 198px; border: 1px solid black; }
            div.col1 { float: right;}
            div.col2 { float: left;}
        </style>
    </head>
    <body>
        <div style="width: 400px;">
            <div class="col1"> <p>One name</p> <textarea id="one"></textarea></div>
            <div class="col2"> <p>Two name</p> <textarea id="two"></textarea></div>
            <div class="col1"> <p>Ten name</p> <textarea id="ten"></textarea></div>
            <div class="col2"> <p>Abc name</p> <textarea id="abc"></textarea></div>
            <div class="col1"> <p>Xyz name</p> <textarea id="xyz"></textarea></div>
            <div class="col2"> <p>Pqr name</p> <textarea id="pqr"></textarea></div>
        </div>
    </body>
</html>
dave mankoff
Thanks. (And yeah, I'll leave out the "div" from the class names:)) So is the "198px" because it's "400px/2 - 1px"? And could I make it occupy the full width of the page instead of 400 px?
ShreevatsaR
yes, he only put the borders to show you the layout (i think), so if you set border:0px you could get the 400px
DrG
Yup. Borders are for demonstration purposes.
dave mankoff
Hmm, doesn't seem to actually work -- the columns are not what I wanted. (I wanted "col1 col2 col2 col2 col1 col1"; maybe I should have picked less confusing names to ask this question...)
ShreevatsaR
This won't work if the boxes vary in height at all, unfortunatetly. One way to fix that might be to put a clear: right; on col2.
dave mankoff
+1  A: 

When I hit that kind of problem I usually go with Tables.

<table>
    <tr>
        <td>
            <p>One name</p> <textarea id="one"></textarea>
        </td>
       <td>
            <p>XYZ name</p> <textarea id="xyz"></textarea>
        </td>
    </tr>
   ....
 </table>

Alignement works perfectly with all browsers.

LeJeune
I think he wants to maintain the div structure for semantic reasons. However, table layouts are possible still - you can use the "display" property to convert elements into tables, table rows, and table cells, at least visually. No IE support, however :(
dave mankoff
I can't put it in a (static) table, because the divs themselves are generated dynamically.
ShreevatsaR
A: 

Tables are a perfectly acceptable solution for this IMHO.

The backlash against tables was directed towards using them for the entire site layout. But if you need to display your data in rows and columns...... that's what tables were made for :)

If you still are interested in a viable cross browser CSS solution I would look at using a framework. It will save you hours of time in trying to get your page to look right.

http://www.blueprintcss.org/ is an excellent framework IMHO.

Andy Webb
A: 

Perhaps you can go with absolute positioning? It depends of course on the rest of your page's structure, but often this is quite viable.

Vilx-
+1  A: 

You have Javascript - why not use it?

You said this:

These divs are generated dynamically if the browser has Javascript, and I don't want to change the actual order that is displayed in the absence of Javascript, for semantic reasons...

You're only displaying these <div>s if the user has Javascript - why not use Javascript to rearrange them? If you move #4 to be after #5, it looks fine with your current CSS.

So (with jQuery):

$("div:eq(3)").remove().insertAfter("div:eq(4)");
Nathan Long
+2  A: 

I'll leave the rest of this answer in place so that someone else can see what my initial thoughts were, but this approach actually fails and requires that there are always more floating elements than none floating.

I think this is your solution (not tested).

.left { width: 51%; float: left; }
.right { width: 49%; }

The 51% stops them from floating next to each other and then just let all the .right content wrap up around those floated blocks.

Thinking less often gives you more with HTML/css, and again - NO to using tables for layout.

===[edit]===

I ran a test on this and it does work with a couple of tweaks AND if you know the first item is floating left (or right to reverse the behavior).

<style>
  div div { text-align: center; padding: 20px 0; overflow: hidden; }
  .left { width: 251px; float: left; background: red; }
  .right { width: 249px; background: green; }
</style>
<div style="width: 500px;" >
  <div class="left"> <p>1</p> </div>
  <div class="right">  <p>2</p> </div>
  <div class="left">  <p>3</p> </div>
  <div class="left">  <p>4</p> </div>
  <div class="right"> <p>5</p> </div>
  <div class="right"> <p>6</p> </div>
</div>
Steve Perks
Good advice, but it doesn't work, unfortunately. :(
ShreevatsaR
I does work with a couple of changes and a precondition that you can probably work around if need be. I edited my response with code in there.
Steve Perks
This produces: 1 2 <br/> 3 5 <br/> 4 6, NOT 2 1 <br/> 3 5 <br/> 4 6. I think you really do have to reorder your elements somehow.
Nathan Long
@Nathan - it works. The condition here is that the first item has to float AND be wider than the wrapping item. I would say that if it's possible to identify which way the first item should float, this is totally doable. I'll put up another offering that incorporates this and jQuery
Steve Perks
@Steve - your approach seems clever, but I copied your code exactly and I see #1 to the left of #2. How can CSS reorder those? I thought a later element couldn't float higher than an earlier one.
Nathan Long
the later element isn't floating though. when you float 2 items, the 3rd unfloated item will just lay into the space provided. I did switch the content, but just to confirm that the first item needs to be the floated one.
Steve Perks
I bow out of this issue. This is not the solution. Explanation in my answer
Steve Perks
Well darn! I was hoping you would prove me wrong. :) I think you went about it in an interesting way, though, and I will keep it in mind.
Nathan Long
Are you averse to using JavaScript? If not, and my wifi holds up, I'll see if I can't knock something out that'll do it pretty quickly for you.
Steve Perks
A: 

I felt guilty that the pure css method didn't work, but if you don't mind using JavaScript to get what you're after, then here's some jQuery that'll work (someone else will probably be able to clean this up for you if you don't like the bloat).

<style>
  #container { width: 500px; border: 1px grey solid; overflow: hidden; }
  #container .rightSide { width: 250px; float: right; }
  #container .left { width: 250px; background: red; padding: 20px 0; overflow: hidden; text-align: center; }
  #container .right { width: 250px; background: green; padding: 20px 0; overflow: hidden; text-align: center; }
</style>
<script type="text/javascript">
$(document).ready(function() {
  $('#container').prepend('<div class="rightSide"></div>');
  $('#container div.right').each(function() {
    var $rightContent = $(this).remove().html();
    $('.rightSide').append('<div class="right">' + $rightContent + '</div>');
  });
});
</script>
<div id="container" >
  <div class="right"> <p>1</p> </div>
  <div class="left">  <p>2</p> </div>
  <div class="left">  <p>3</p> </div>
  <div class="left">  <p>4</p> </div>
  <div class="right"> <p>5</p> </div>
  <div class="right"> <p>6</p> </div>
</div>

Cheers,
Steve

Steve Perks
I so dislike down votes with no explanation of why they find the information to be wrong. It's interesting how this solution, which is more correct, gets down voted, where my failed attempt at using css alone gets up votes.
Steve Perks
Just noticed this... +1
ShreevatsaR
+1  A: 

Solution 1

If you can afford to duplicate HTML:

.left-column .right, .right-column .left {
    display: none;
}

Solution 2

Wait a little until browsers support CSS3:

.right {
    position: flow(side); 
}

Solution 3

If your divs have the same height, use the indirect adjacent sibling combinator. Even though it belongs to CSS Level 3, it's already well-supported:

.container {
    position: relative;
}

.left, .right {
    position: absolute;
    top : 0;
    width: 50%;
} 

.left {
    left: 0;
}

.right {
    left: 50%;
}

.left ~ .left, .right ~ .right {
    top : 100px;
}

.left ~ .left ~ .left, .right ~ .right ~ .right {
    top : 200px;
}

.left ~ .left ~ .left ~ .left, .right ~ .right ~ .right ~ .right {
    top : 300px;
}

... /* you need to include as many rules as the maximum possible height */
ilya n.
A: 
Anders