Offsetting an HTML element in a flexible container
This morning, while I was preparing the materials for the second edition of Responsive Web Design workshop I revisited some of the questions that attendees had asked the last time. One question that comes up frequently — even outside the workshops — is how to handle flexible widths and paddings on a child element, if the container itself is set with flexible lengths.
The box-sizing property set to border-box is definitely a big help and people usually understand it right away. It’s probably because that’s more natural way of perceiving the CSS box model.
To the left, to the right
Say, we have a definition list and we want to create a typographic grid of thirds. The definition title hangs on the left and is 1/3 wide, while the definition description goes to the right and is 2/3 wide.
<dl>
<dt>This goes to the left.</dt>
<dd>This goes to the right.</dd>
</dl>
That is a pretty common arrangement and a useful one when you need a better page scannability. To give you an idea for some typographic options, there are two standard variants. The first one is with the hanging element (e.g. the title) flushed right to meet the main text (e.g. the description) at the gutter between two columns, and the second one is with both elements simply flushed left.
Shaping the grid
If you set the box-sizing and the width of the container to border-box and 100% respectively, you can shape a 2-thirds definition description column by simply applying the left padding of 33 per cent to the definition list. (For the sake of clarity, let’s pretend that 33% and 66% form the exact 1-third / 2-thirds split of 100%. Check out the final code for the fully working example.) The definition description would fill the remaining space and the resulting width of the definition description column would be 66 per cent.
dl {
box-sizing: border-box;
width: 100%;
padding-left: 33%;
/* remaining width: ≈ 66%; */
}
dd {
/* calculated-width: ≈ 66%; */
}
Floating elements with a negative margin
The second and the final step is to shift the definition title to the left and lay it in the empty space that has been created when you added the left padding to the definition list element. The technique is simple: float the definition title to the left, give it a negative left margin and apply the width equal to the width of the empty space, i.e. equal to the left padding of the definition list. I hope you are following so far.
Here’s a question: What is the width of the definition title? If you automatically answered 33 per cent, it is because it corresponds to a visual equivalent of the one third of the container element. Unfortunately, this is not the correct answer.
dt {
float: left;
width: ??%;
margin-left: -??%;
}
However, in CSS the 100 per cent width of an element equals to a space available inside the parent element. Since the available space is in our case 66 per cent, the definition title width of 100 per cent equals the 66 per cent of the definition list. It follows that if we want the definition title to always be 3 times narrower than the definition list (1/3 of the definition list), we should set its width to 50 per cent, i.e. the 50% of the remaining 66% formed after you had added the padding of 33%.
Confused? Me too! Worry not, here’s the easy-peasy formula to calculate the width of the offset element for any percentage based combination:
x = container’s left padding in %
offset element’s width in % = (x / (100 - x)) * 100
Now that we have the width set in place, the only thing that has left is setting a negative number of the same value for the left margin. Take a look at the final result:
dl {
box-sizing: border-box;
width: 100%;
padding-left: 33.3333333%;
}
dt {
float: left;
width: 50%;
margin-left: -50%;
}
Feel free to try the formula with any combination, for instance 20% + 80% or 40% + 60%. Here’s the example of how to offset the title with the Golden ratio based setup:
dl {
box-sizing: border-box;
width: 100%;
padding-left: 37.88819876%;
/* remaining width: 62.11180124% */
}
dt {
float: left;
width: 61.000000006%;
margin-left: -61.000000006%;
}
Need support for IE7 and predecessors? Check out the Emulate CSS box-sizing in IE7 article.