The CSS Box Model

Prerequisites

This article only assumes that you understand the basics of HTML and CSS. If you are familiar with HTML elements and basic CSS selectors like p or .blue-text and CSS properties like font size or width, you will be fine.

Building Up Our Mental Model

The most accurate way to describe an HTML document is in the shape of a tree. It begins with the HTML element, and each document component is a decedent of that tree. You can see an example below.

A tree diagram of a simple HTML document

Although this graphic does not accurately display the nesting, this is the natural form of an HTML document and how the Document Object Model(DOM) represents it. But let's put that in our back pocket, for now, because it is irrelevant to how I want you to think about things in the context of this article.

Instead, I want you to think of your HTML document as not too dissimilar from a set of Matreshka dolls.

A line of Matreshka dolls

I like to believe everyone's grandmother had a set of these funny little dolls in their home, but in case anyone is unfamiliar, allow me a brief detour. The largest doll represents the matriarch of the family. Open it up, and you will find a slightly smaller doll, and then again and again to reach the smallest.

The way we outline the layout and components of our user interfaces is very similar. The HTML element is the top-level box that contains our entire document. Nested inside is the body element, which in turn includes our other elements, such as a paragraph or a list. This is the foundation of the CSS box model, which is the system browsers use to determine how to render a piece of UI, namely, its dimensions.

Quick Note: Positioning

The information presented from this point down will refer exclusively to items that are statically positioned, which is the default for all elements unless explicitly changed. The rationale for this is that positions of absolute or relative have very nuanced behavior and will eventually be an article all their own. Understanding static position first will lay a solid foundation that makes debugging those quirkier setups significantly easier.

Flex and grid will be omitted for the same reason.

Display Types: Block and Inline

The first step of conceptualizing the box model is understanding the various types of elements you may potentially use.

The most common type of element is block level elements, with a few common examples being div, p, and ul elements. The default behavior of a block-level element is that its height is equal to its content (more on this later), and it spans the entire width of its parent container. You are also able to set alternative values for the height, width, margin, and padding properties. Even if you override one of these properties, such as setting the width to 50%, the browser will insert a line break after each element so each block element will appear under the one before.

An alternative is an inline element, some common examples being span or img. The default behavior of these elements is that they do not receive the line break after each element like block-level elements do, and each appears one after the other. Your options for customization are more constrained as well, as you can apply margin and padding to the left or right side, but placing either on the top or bottom is ignored by the browser. You also cannot apply modifications to their width or their height.

Although you can't override the height of an inline element, the height is not necessarily static. For example, if you put a span with a small amount of text on either side of an image, the container will grow to the height of the image, and the text will appear at the bottom.

There is a hybrid of both of these types, called inline-block. The primary difference is that inline-block elements will still appear side-by-side, but you are able to apply width or height. Padding and margin can also be added to the top and bottom as well, not simply the left and right.

Box Sizing and the Box Model

Now with the foundation out of the way, we are going to turn the "boxception" up to 11. In addition to being able to think of each element in the document as being boxes inside of boxes, each element is also composed of a few smaller boxes: margin, padding, content, and border boxes.

A diagram of the box model showing each box that composes the element
Credit: MDN

As the name suggests, the content box is the very-most center box that actually houses the content, like the text inside of a paragraph. The border-box wraps around the content box. Even if there is no border (i.e., border: none), this box is still present. Then finally, our padding is the space that sits between the content box and the border box, while the margin is outside of the border box.

Practical Exercise

Let's step through a simple exercise that is an example of where most, if not all, CSS newbies get bitten by box-sizing. Suppose you create a div in your code and apply the following styles:

.example {
  width: 100px;
  padding: 25px;
  border: 3px solid #000;
}

Wait a minute; now our div has an actual rendered width of 156px. What just happened?

The default box-sizing model used by the browser is called content-box sizing. This means that adjustments to height or width are applied to the content box. Remember that this is our innermost box, and margin, padding, and border boxes will all add additional size as directed by our other CSS rules. So the CSS rule set above gives us an initial content box size of 100px but then adds 25px of padding to the left and right side, and finally 3px of the border to the left and right side, which produces our final calculated width of 156px.

An Alternative: Border Box Sizing

There is an alternative to a box-sizing of content-box, and that is border box-sizing. Nothing changes about the behaviors described above, such as inline-level elements versus block-level elements, but instead of our width property is applied to the content box, it is applied to the border box.

With a small tweak to the CSS above:

.example {
  box-sizing: border-box;
  width: 100px;
  padding: 25px;
  border: 3px solid #000;
}

Our div now has the expected width of 100px. But make a note of the fact that other attributes, such as margin or padding, still apply, so this model now works in reverse. What do I mean by that? Essentially just that now instead of the additional 28 pixels being applied to the base width of 100 pixels, those values are now subtracted from the content box. So the content box of the element was reduced to just 44 pixels. So keep this in mind.

That said, a lot of other people who work with CSS and I feel as though this is still a much more sensible mental model. Because of this, a lot of CSS frameworks, CSS resets, and CSS normalizers will automatically set the box-sizing of all elements to border-box. If the behavior of the example above seemed strange or unexpected, you might already be using something like normalize.css that resets this default.

You can accomplish this with the following CSS:

* {
  box-sizing: border-box;
}

Conclusion

As a reminder, there are other scenarios where behavior will deviate from the above. Such as setting the display property on an element to alternatives like flex, grid, inline-flex, or inline-grid. CSS is a simple language syntactically, but can become complicated because of the nuance of it's behavior and the browsers interpretation of your rule sets, but I hope now you feel more confident in your ability to style things how you wish and more consistently nail the expected outcome.