Column-swapping: an attempt for two columns with equal height

Background

As discussed in the article "Faux Columns" by Dan Cederholm [1], "elements only stretch vertically as far as they need to" (rendered in standards-compliant mode).

Eric A. Meyers "Cascading Style Sheets" [2] is instructive to me on this matter. In Chapter 10 "Floating and Positioning: Negative margins" a discussion of what happens when negative values for positioning are used in floated elements reveals that there is a shifting "content edge", and remarkably, an "outer edge" which stays the same, keeping it's position as if it were not shifted.

Concept

The idea is to swap both columns, the right one to the left, the left one to the right: Note, when we shift a column to the right, the "outer edge" will remain on the left-hand side and vice versa. Therefore, the "outer edge" and the "content edge" of one column will stretch both containers. So, no matter which of the columns is the taller, the tallest will stretch both containers!

This can be done in CSS in standards-compliant mode, without too many browser specific hacks. All paragraphs should be placed in sequence order to assist screen-readers, along with a sizeable width for zooming the text. Each column should have the ability to have its own background-setting.

Implementation

We need a wrapper, sized using ems in horizontal direction, wide enough to hold two columns and a dividing space in the middle (Fig. 1).

Its first child, .containerRight (Fig. 2), is a right-floater, its second child, .containerLeft (Fig. 3), is a left-floater.

The column is a child of the container, positioned relative (Fig. 4). So what happens if we shift it to the other side with the property "left"? The "outer edge" will stay on the right of the column, stretching the container by the columns calculated height (Fig. 5).

So when swapping both columns to their other side (Fig. 6a, 6b, 7) they remain with their "outer edge". Since the shifted "content edge" of the tallest column overlaps the non-shifted "outer edge" of the container on its side both columns are stretched. Therefore, as a consequence of the overlapping, both columns are now shown at equal height.

Compatibility

Working in

  • Win: Moz1.8a3, Moz1.7, FF1.0PR, FF.9, Opera7.54, NN7, IE6, IE5.5
  • Mac: IE5.23, IE5.17, Safari1.22, Omniweb5.0rc2, Moz1.8a3
  • Linux: Konqueror3.2.3, Moz1.8a3

Not working in

  • Opera 6.03/Lin, Konqueror3.03/Lin, NN4.7

Discussion

It's an em-based design, as I didn't manage an exact cross-browser shifting version using percentages, though a px-fixed layout should work too. There is no need for any background setting. Also, a shifting method using margin-left should be possible if needed.

Opera7.54 needs z-indexes for the columns: Without them, the background of the column-container is patched over the shifted column, that's bad render sequencing ... (Fig. 8). The shifting works for each column, but together, the container planes were drawn in order of the markup after the shifting. In IEWin/Mozilla, this is the behaviour when both containers are given {position: relative;}.

IE mac needs a hack: when applying {left: 22em;} to .columnShiftToRight, as the .containerLeft expands in width. Therefore, in order to fix this, {display: inline-block} (CSS2.1) is presented to IE mac only. The columns, shifted by the use of the property "left:", need {overflow:auto} to prevent the shifting on nested elements [3].

Acknowledgement

Many thanks to Big John at Position is Everything for a site review and for encouraging me, and to Philippe Wittenbergh at La Chatte Noire for his invaluable Mac engineering and fixing.

Thanks to David Batty for corrections.

Arnau Siches explains the method in Catalan: Igualant columnes.

Revisited

We're discussing the needs of two columns with different em-width. IE6 ("standards" mode), FF1+, Op8 are fine. It breaks IE5Mac, though: column-swapping, revisited

Companion column method is a new, different approach for the old problem.

Last updated: July 10, 2007

Created: August 5, 2004

Any ideas, workarounds, comments and corrections are appreciated. Note that this method hasn't been tested for production sites yet, the status of this document is still "draft, experimental". Please report your stress testing of this method to: info at satzansatz dot de.

Ingo Chao

more CSS related stuff


 
.wrapper{
    margin: 0 auto;
    width: 42em;
    background: #f2f2f2;
    }

Fig. 1: The centred .wrapper can be sized in ems. It holds both columns. We could also place a dividing background-image, a header and a footer, etc. For illustration, the width is set to 42em for a 2em gap between the columns (therefore, the distance shifted for a 20em-column must exceed to 22em /-22em).

 
.containerRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    float:right;
    }

<div class="wrapper">
  <div class="containerRight">
   ...
  </div>
</div>

Fig. 2: The .containerRight is a right-floater. So, when the column that will be placed here later is given its background-image, the container must also have the same image too .


 
.containerLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    float:left;
    }

<div class="wrapper">
  <div class="containerRight">
   ...
  </div>
  <div class="containerLeft">
   ...
  </div>
</div>

Fig. 3: The .containerLeft is a left-floater, it's the second child of the wrapper.


 
.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    /* left: -22em; */
    /* not shifted in this Fig.*/
    position: relative;
    z-index:1;
    }

<div class="wrapper">
  <div class="containerRight">
    <div class="columnShiftToLeft">
     ...
    </div>
  </div>
  <div class="containerLeft">
   ...
  </div>
</div>

Fig. 4: The .columnShiftToLeft should have the images, colors and borders set according to a left side position. Not shifted yet in this figure. Note the background-image on the right hand side.


 
.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    left: -22em;
    /* shifted */
    position: relative;
    z-index: 1;
    }

<div class="wrapper">
  <div class="containerRight">
    <div class="columnShiftToLeft">
     ...
    </div>
  </div>
  <div class="containerLeft">
   ...
  </div>
</div>

Fig. 5: Now .columnShiftToLeft is shifted. Its parent stays on the other side, shown here for illustration with a colored background.

shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - END


 
.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    left: -22em;
    position: relative;
    z-index: 1;
    }

shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - END

 .columnShiftToRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    left: 22em;
    position: relative;
    z-index: 2;
    }

<div class="wrapper">
  <div class="containerRight">
    <div class="columnShiftToLeft">
     ...
    </div>
  </div>
  <div class="containerLeft">
    <div class="columnShiftToRight">
     ...
    </div>
  </div>
</div>

Fig. 6a: Now .columnShiftToRight is shifted too. The container of the taller column can be seen under the shorter column. Note what would happen if there was no background-image and no background-color in the parent of the other column, .containerRight.

shifted content - right - - END


 
.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    left: -22em;
    position: relative;
    z-index: 1;
    }

shifted content - left - END

 .columnShiftToRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    left: 22em;
    position: relative;
    z-index: 2;
    }

<div class="wrapper">
  <div class="containerRight">
    <div class="columnShiftToLeft">
     ...
    </div>
  </div>
  <div class="containerLeft">
    <div class="columnShiftToRight">
     ...
    </div>
  </div>
</div>

Fig. 6b: No matter which column is taller, both columns will stretch to equal height and the containers will now have the same color and background-images as the shifted columns.

shifted content - right - END


 
body {
    margin: 2.5% 0 2.5% 0;
    background-color:#F2F2F2;
    font-family: sans-serif;
    }
.ccontainweb {
    width:100%;
    text-align:center;
    background-color:#F2F2F2;
    margin:0;
    padding:0;
    }

.ccontainpage {
    padding: 2% 2.5% 3% 2.5%;
    margin:0 auto;
    width:90%;
    text-align:left;
    background-color:#FFFCF5;
    border: 1px solid #8F8F8F;
    }

.wrapper{
    margin: 0 auto;
    width:42em;
    background: #f2f2f2;
    }

.header{
    margin-bottom: 1em;
    text-align:center;
    background:#ddd;
    }

.footer{
    margin-top: 1em;
    background:#ddd;
    }

.containerRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    float:right;
    }

.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    left: -22em;
    position: relative;
    z-index:1; /* Opera7.54 */
    }

/* IE mac hack */

* html>body .columnShiftToLeft {
    overflow: auto;
    }

.containerLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    float:left;
    }

.columnShiftToRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    left:22em;
    position: relative;
    z-index:2; /* Opera7.54 */
    }

/* IE mac hack */

* html>body .columnShiftToRight{
  display:inline-block;
  overflow: auto;
  }

.content{
    padding: 1em;
    line-height: 125%;
    }

.brclear {
    clear:both;
    height:0;
    margin:0;
    font-size: 1px;
    line-height: 0;
    }

<body>
<div class="ccontainweb">
<div class="ccontainpage">

<div class="wrapper">
    <div class="header">
        <div class="content">
        ...
        </div>
    </div>


    <div class="containerRight">
        <div class="columnShiftToLeft">
            <div class="content">
            ...
            </div>
        </div>
    </div>

    <div class="containerLeft">
        <div class="columnShiftToRight">
            <div class="content">
            ...
            </div>
        </div>
    </div>

    <br class="brclear" />

    <div class="footer">
        <div class="content">
        ...
        </div>
    </div>
</div>

</div>
</div>
</body>

Fig. 7: Both columns are shifted. Each column has a child div.content for paddings etc. Note that the central gap can be closed by appropriate settings of width and left in .wrapper, .containers, .columns:

.wrapper{
    margin: 0 auto;
    width: 40em;
    background: #f2f2f2;
    }

.columnShiftToLeft{
    width: 20em;
    background: #ffedc7
      url(swapborder_l.png)
      repeat-y right;
    left: -20em;
    position: relative;
    z-index:1;
    }
* html>body .columnShiftToLeft {
    overflow: auto;
    }

 .columnShiftToRight{
    width: 20em;
    background: #ffedc7
      url(swapborder_r.png)
      repeat-y left;
    left: 20em;
    position: relative;
    z-index:2;
    }

/* IE mac hack */

* html>body .columnShiftToRight{
  display:inline-block;
  overflow: auto;
  }

 

shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - shifted content - left - - END

Fig. 8: Thats the situation in Opera 7.54/Win without z-index.The second .containerLeft gets a higher index in the z-direction than the child column of the first container.