2. Box sizing in CSS
In this chapter, I will cover how the boxes generated by HTML elements are sized given a particular positioning scheme. The CSS box model is the basic structure that defines the components of a box in CSS.
The sizing of boxes is related to the box model, but it is strongly influenced by the positioning scheme that is used. Many CSS tutorials start with the box model before introducing positioning schemes, but I've switched the order because you cannot really understand how margins and content dimensions are calculated for the box model unless you can talk about boxes in the context of a positioning scheme.
If you've ever read a book on CSS, you've probably seen something like the figure below, which illustrates the box model:
As the spec states:
Each box has a content area (e.g., text, an image, etc.) and optional surrounding padding, border, and margin areas; the size of each area is specified by properties defined below.
... each box in CSS has several parts:
- a margin
- a border
- a padding
- the content width/height
Here are the default values for these properties, as well as some notes on their purpose.
Property | Default value | Valid values | Purpose |
---|---|---|---|
margin | 0 | length, percentage or auto |
Controls the size of the margin. Top and bottom margins have special behavior when interacting with other margins, known as margin collapsing. Negative values are allowed and affect collapsing behavior. margin: auto can be used for centering boxes. Top and bottom margins have no effect on (non-replaced) inline elements. Percentages refer to width of the containing block, even for margin-top and margin-bottom . |
border | medium none currentColor | length, border style, color or transparent | Controls the size, style and color of the border. Rendered differently for inline and block elements. |
padding | Varies | length or percentage | Controls the size of the padding. Negative values are not allowed. Percentages refer to width of the containing block. |
width, height | auto | Controls the dimensions of the element. display: inline elements cannot have a width or a height. |
I'm going to assume that you are familiar with the visual styles that you can achieve with borders and focus mostly on the content dimensions and positioning aspects of these properties.
Content dimensions and margins
The padding
and border
properties work mostly in a consistent manner: they produce padding around the content box and borders that surround the content box. The only major edge case is that on inline-level elements (excluding inline-block), the left and right borders are only drawn once if the content is broken over multiple lines rather than being drawn for each line box, and that the top and bottom borders do not affect the vertical layout.
The example below illustrates:
<p class="blue">Lorem ipsum dolor sit amet, sed nulla, dignissim suspendisse libero massa erat tempor.</p>
<p><span class="green">Lorem ipsum dolor sit amet, sed nulla, dignissim suspendisse libero massa erat tempor.</span></p>
In the example above:
- the first paragraph has a blue border applied to it. Since the border is on a block-level box, it is rendered as a single, continuous box.
- the second paragraph contains a span element with a green border applied to it. Since the border is on an inline-level box, the left and right borders are only drawn at the end, and only the top and bottom borders are drawn on each line of content.
The two more interesting aspects of the box model concern the calculation of content dimensions and the effect of margin: auto
on different element types.
I will only discuss these properties in the context of non-replaced elements to avoid making this section any longer than it is. Non-replaced elements are elements that have a definition in HTML/CSS, such as text and regular box-generating blocks. Replaced elements are elements such as video and images, and they essentially have some externally defined sizes which are used to determine a usable size. The special rules for non-replaced elements are extensive, but if you are curious you can read Chapter 10, "Visual formatting model details" in the CSS 2.1 spec.
Let's start off with a quick summary of the mechanisms by which content dimensions (width and height) and automatic margins (margin: auto) are calculated. These differ based on whether a box is inline, block, floated or absolutely positioned. In addition, display: inline-block
boxes have special behavior. The table below summarized the methods used to calculate values when width
, height
or margin-*
is set to auto
.
Box type | Height | Width | Margin (Left/Right) | Margin (Top/Bottom) |
---|---|---|---|---|
Inline | N/A | N/A | auto -> 0 | N/A |
Block | auto -> content-based | auto -> constraint-based | auto -> center | auto -> 0 |
Float | auto -> content-based | auto -> shrink-to-fit | auto -> 0 | auto -> 0 |
Inline-block | auto -> content-based | auto -> shrink-to-fit | auto -> 0 | auto -> 0 |
Absolute | special | special | special | special |
Box model calculations for inline elements
Inline, non-replaced elements are the easiest to understand:
width
and height
are ignored for inline-level elements. Instead, width and height are determined by the (text) content dimensions.
Inline-level elements are positioned by placing them on line boxes. Line boxes are sized based on font-size
and line-height
as described in the section on the inline-level formatting context in the previous chapter.
margin-top
and margin-bottom
are ignored for inline-level elements.
margin-left
and margin-right
do work for inline blocks. Setting these properties causes the inline boxes to be offset from other content on the same line box. For both properties, the default value auto
is simply interpreted as margin-left: 0
, and no special processing takes place.
"Content-based" height for blocks, floats and inline-blocks
As you can see in the table, block-level elements, floats and display: inline-block
elements all have the same behavior when resolving the default value auto
for height
.
height: auto
is interpreted as "content-based" height for these elements. That is, after positioning the children of the element, take the the bottom edge of the last child and set the height of the element to match.
The spec actually has two "content-based" height calculations:
- one for floats, inline-block elements and block-level elements in normal flow when
overflow
does not compute tovisible
(relevant section) - another one for block-level elements in normal flow when
overflow
does compute tovisible
(relevant section)
Both of those calculations attempt to set the height
of the element to account for the contents, but the algorithm used for block-level elements when overflow: visible
ignores floating descendants. This is the reason why, by default, a block-level box with only floating descendants has a height of 0 since visible
is the default value for overflow
.
For floats, inline-block elements and block-level elements in normal flow where overflow is some value other than visible
, floating descendants are also taken into account. Specifically, for those elements:
In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge, then the height is increased to include those edges. Only floats that participate in this block formatting context are taken into account, e.g., floats inside absolutely positioned descendants or other floats are not. source
In other words, by default (auto
is default for both width
and height
), these blocks will always expand such that they can fit all of their content unless you specify an explicit height.
Width calculations
Width calculations are more complicated, with two different algorithms for filling in values of width
and margin
that are set to auto
.
Block-level elements use a "constraint-based" approach. The constraints are defined by the box model (e.g. border, padding, margin). If either width
or margin
is set auto
, then the auto
value is filled in by taking the usable space, subtracting any values that have been explicitly set and setting the auto
value to the result.
Floating blocks and inline-block elements use a "shrink-to-fit" approach. This involves calculating 1) the preferred width (e.g. using as few line breaks as possible), 2) the preferred minimum width is available (e.g. using as many line breaks as possible) and 3) the available width.
The width
value is set to the preferred width if horizontal space is available, otherwise it is set to the preferred minimum width and in the worst case to the available width with some potential overflow. Note that margin: auto
is always interpreted as margin: 0
for floating blocks and inline-block elements.
Width calculations: block-level elements (constraint-based)
Here's how the spec describes the constraint-based approach used for block-level elements:
The following constraints must hold among the used values of the other properties:
'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
Because border
and padding
cannot be set to auto
, this really reduces down to:
margin-left + width + margin-right = width of containing block
When all three of these values are set explicitly, the values set are used. The constraint-based approach comes into play when:
- when
width
isauto
and the margins areauto
- when
width
is set explicitly and bothmargin
s areauto
- when two of the three values are set explicitly, and one is set to
auto
In the first case, when width
is auto
and the margin
s are auto
, set the margins to 0
and then calculate the width:
If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
In this case the box expands to use the full width, taking into account any space needed for borders and padding:
.width-auto {
width: auto;
margin: auto;
}
<div class="width-auto blue">width: auto, margin: auto</div>
In the second case, when width
is set and the margin
s are auto, center the box:
If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the edges of the containing block. source
The box width is fixed, and the margins are set so that the box is centered:
.margin-auto {
margin: auto;
width: 100px;
}
<div class="margin-auto blue">width: 100px, margin: auto</div>
Of course, the problem with this method of centering is that the box width has to be set explicitly (and that you cannot center vertically, since a block formatting context positions block boxes sequentially from top to bottom).
Finally, given two values that are set and one value that is auto
, use the constraint equality to calculate the auto
value:
[...] If there is exactly one value specified as 'auto', its used value follows from the equality.
This allows you to align a block-level to the left or right hand side of the container box, for example:
.flush-right {
margin-left:auto;
margin-right:5px;
width: 100px;
}
<div class="flush-right blue">width: 100px, margin-left: auto, margin-right: 5px</div>
You can also set both margins explicitly, and have the width of the block take up the remaining space.
Width calculations: floating blocks and inline-block elements (shrink-to-fit)
Here's what the spec says about non-replaced floating blocks and inline-block elements:
If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm. Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur, and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2.1 does not define the exact algorithm. Thirdly, find the available width: in this case, this is the width of the containing block minus the used values of 'margin-left', 'border-left-width', 'padding-left', 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
Here's how this description works out with real markup. When plenty of width is available, the preferred width is used:
.inline-block {
display: inline-block;
}
<div class="big blue">
<div class="inline-block orange">AAAAAAAAA BBBBB</div>
</div>
When the available width is less than the preferred width, but greater than or equal to the preferred minimum width, the available width (> preferred minimum width) is used:
.big {
width: 130px;
}
.inline-block {
display: inline-block;
}
<div class="big blue">
<div class="inline-block orange">AAAAAAAAA BBBBB</div>
</div>
When the available width is less than the preferred minimum width, the available width (< preferred minimum width) is used and the content may overflow:
.big {
width: 80px;
}
.inline-block {
display: inline-block;
}
<div class="big blue">
<div class="inline-block orange">AAAAAAAAA BBBBB</div>
</div>
Margins for floating blocks and inline-block elements
Margin calculations for floating blocks and inline-block elements are simple. Floating blocks and inline-block elements, setting any margin to auto
is equivalent to setting it to 0
.
Absolutely positioned, non-replaced elements
Absolutely positioned elements use a combination of the constraint-based and shrink-to-fit / content-based algorithms for both horizontal and vertical positioning.
The constraints are:
'top' + 'margin-top' + 'border-top-width' + 'padding-top' +
'height' + 'padding-bottom' + 'border-bottom-width' +
'margin-bottom' + 'bottom'
= height of containing block
and:
'left' + 'margin-left' + 'border-left-width' + 'padding-left' +
'width' + 'padding-right' + 'border-right-width' + 'margin-right' +
'right'
= width of containing block
We can simplify these conceptually by ignoring both padding and borders, since they do not support a value of auto
and hence will always be a specific size or 0
:
'top' + 'margin-top' + 'height' + 'margin-bottom' + 'bottom'
= height of containing block
and:
'left' + 'margin-left' + 'width' + 'margin-right' + 'right'
= width of containing block
Even more concisely: the content + offsets from the container box + margins must add up to the container's dimensions.
The following logic covers the majority of cases, given the five properties (width + the left & right margins + left & right offsets, or height + the top & bottom margins + top & bottom offsets):
- if all the properties are set to explicitly, then use those values.
- if the content width/height and the offsets are set, and the margins are
auto
, then solve the constraint equation with the additional constraint that the margins get equal values (in other words: center the content by setting the margins) - if the content width/height and the offsets are set, and one margin is
auto
, use the constraint-based approach to calculate the missing margin's value - otherwise, consider margins with
auto
to equal0
and look at the three remaining properties (width + offsets or height + offsets):- if none of those properties is set to
auto
, we'd already have handled that case above since the values were all set explicitly. - if only one of the three properties is set to
auto
, then a constraint-based calculation is to calculate the missing property. - if two properties are set to
auto
and one of those properties iswidth
/height
, then use the shrink-to-fit or content-based approach to calculatewidth
/height
and then use constraint-based approach to calculate the other missing value. - if all three properties are
auto
, position the element as if it was statically positioned fortop
orleft
, then calculate the content size using shrink-to-fit (for width) / content-based (for height) sizing, then calculate thebottom
/right
using the constraints
- if none of those properties is set to
The other cases can be considered not typical, and you can read the full description for width in the spec and height in the spec.
As you can see, the rule of thumb is that if the content dimensions are unspecified, they are calculated using the shrink-to-fit (for width) / content-based (for height) algorithm before attempting to fill in the rest of the values through the constraints specified by the box model.
Centering horizontally and vertically is possible using absolute positioning. However, there are two major caveats:
- absolutely positioned elements do not interact with later sibling elements and may be drawn on top of any content in normal flow / sibling floats.
- in order to trigger the positioning, content dimensions (
width
/height
) and offset positions (left
/right
/top
/bottom
) must be set.
The latter caveat seems rather major: you need to declare some content dimensions. However, there is a workaround: you can declare the content dimensions by using percentages rather than pixels, and you can also use max-width
and max-height
(which will be discussed in a bit) to further provide some responsive sizing.
In fact, there are three CSS techniques that allow you to perform centering which are based on position: absolute
. I will discuss them in more detail in the next chapter.