Monday, September 14, 2015

The CSS Box Model Explained (or why are my margins all wrong?)

When you're using CSS to specify margin, padding, border, width and height values for your elements, you might run into some issues where the browser doesn't seem to be calculating the values correctly. Sometimes, there might be some actual errors in your code, but in some cases, your code is correct, but the results displayed in the browser are not quite what you expected. For example, you've specified a width of 400 pixels for your paragraph, but when you measured it, it shows a value of 420 pixels instead. Or you're expecting two elements to have a 20 pixel margin space in between them, but the browser only adds 10 pixels.

In cases like these, it's important that you have an understanding of what is called the CSS Box Model.

So what is the CSS box model? First, lets take a look at an example. Here is a very simple HTML code snippet that defines a paragraph:
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent 
pulvinar venenatis posuere. Sed eu ullamcorper enim, a eleifend urna. 
Phasellus in mollis magna, non tristique.</p>
When displayed in a browser, this paragraph will look something like this:


Now try to imagine that this paragraph is contained inside a box.


This is something that your browser will do for each element in your web page's document tree. It puts each element inside a box, and this box is used to determine how the element is laid out. The size of the box is based on the content of the element, along with any values specified for the width, height, margins, paddings, and borders. It's important to note that even if you don't put any borders, each element is always going to have its own box - it could be a box made up of just the content or a box that includes margins and paddings. But whatever the case, there will always be a box, even if you don't see it.

Now in order to make sure that these boxes are rendered correctly, there needs to be a set of rules that the browser can follow in order to be consistent. And that is why the CSS box model was created. The CSS box model provides a standard model that browsers should follow when generating these boxes that determine how the elements in your document are laid out. It provides a set of rules as to how the elements of your document will interact with each other based on each element's content width, content height, margins, paddings, and borders.

This image illustrates the basic CSS box model:


We see that the box is made up of different areas.

Inside, we have the CONTENT AREA, whose dimensions are made up of the content width and content height. The content area, as the term implies, is the space for the element's content. In paragraphs, for example, the content area is the space for the words in the paragraph. In section elements, the content area would be the space for the headings, paragraphs, and all other elements that can be placed insides sections.

Surrounding the edges of the content area would be the PADDING AREA, which is made up of the top, bottom, left, and right paddings.

Then around the padding area, we have the BORDER AREA, also consisting of top, bottom, left, and right segments.

And finally, the outermost part is the MARGIN AREA, which can also be found on all sides.

It's important to note that the CSS borders do not always show the exact size of the box. As you can see, the margins are OUTSIDE of the borders. And because margins are transparent, the box can appear smaller than it actually is. Unless you have a margin of 0 on all sides, the actual box is always bigger than the area illustrated by the CSS borders.

I'd also like to point out that the CSS specification uses the terms content BOX, padding BOX, border BOX, and margin BOX. But to avoid confusion in this article, I'll be using the word BOX to refer to the ENTIRE box, and AREA to refer to the different parts inside it - content AREA, padding AREA, border AREA, and margin AREA.

Ok, so let's take a look at an actual example. Below is a style rule for paragraphs:

p {
width: 400px;
margin-left: 15px;
margin-right: 25px;
}

So what would be the total width of the paragraph's box? Will it be 400 pixels INCLUDING the left and right margins? Or do we have to add the margin values to the existing width value? These are the types of questions that the CSS box model tries to address.

So what does the CSS box model say? According to the CSS box model, width and height refer only to content width and content height. So while the width of the content width is 400 pixels, the width of the actual BOX will be: 400 (content width) + 15 (left margin) + 25 (right margin) + any default left and right paddings that the browser may have added.

Here's another example:

p {
width: 400px;
margin-left: 15px;
margin-right: 25px;
padding: 10px;
border: 5px black solid;
}

Here, we're using shorthand declarations for the padding and the border. The padding statement means that there is a 10 pixel margin for each side. So when calculating for the width of the box, we take into account 10 pixels for the left padding and another 10 pixels for the right padding. Same thing goes for the border. The border statement applies a 5 pixel thick border for each side, so we take into account 5 pixels for the left border, and another 5 pixels for the right. So to compute for the width of the box:
  400 (content width)
+  15 (left margin)
+  25 (right margin)
+  10 (left padding)
+  10 (right padding)
+   5 (left border)
+   5 (right border)
=====
  470 pixels
So our total box width is 470 pixels.

Here is an illustration to help you visualize the box width in this example:


In some cases, you may have element content that doesn't fit inside the box.

For example:

p {
width: 200px;
height: 50px;
margin: 15px;
padding: 10px;
border: 5px black solid;
}

In this example, we are explicitly setting both a content width (200 pixels) and a content height (50 pixels). Usually, the paragraph's box will just automatically adjust its height to fit the content. But here, we're constraining it to 50 pixels. If the paragraph only has 15 or so medium-length words, then the 200 by 50 pixel content area should be enough. But if we have too much, then the content begins to flow out of the box.

Here is how it would look like with a paragraph that has 50 words:


The box height itself would be:
  50 (content height) 
+ 15 (top margin)
+ 15 (bottom margin)
+ 10 (top padding)
+ 10 (bottom padding)
+  5 (top border)
+  5 (bottom border)
====
 110 pixels (total box height)
But clearly, the content of the paragraph is too much to fit inside the box, so we see the overflow spill beyond it. To control the overflow, we can use the overflow property in CSS. By default, this has a value of visible, which means that any overflow will be visible. The other values are hidden (which will hide the overflow) and scroll (which hides the overflow, but adds a scroll bar so we can scroll down to see more).

For example:

p {
width: 200px;
height: 50px;
margin: 15px;
padding: 10px;
border: 5px black solid;
overflow: scroll;
}

Here, a scrollbar will be added for any overflow that the paragraph might contain. However, scrolling controls for overflow is probably better handled using JavaScript, because current cross-browser CSS support for scrollbars is not very good.

Collapsing Margins


In this next part of the lesson, we'll talk about collapsing margins in the CSS box model. But before I define what collapsing margins are, let's take a look at an example. Say we have 2 paragraphs, each one with a different class:
<p class="p1">Paragraph 1</p>

<p class="p2">Paragraph 2</p>
So Paragraph 1 with a class of p1 comes first, and then Paragraph 2 with a class of p2 is right below it. This means that the bottom margin of the first paragraph comes in contact with the top margin of the second paragraph.

So what happens to the margins? Let's say that in our style rule, we specify these values:

p.p1 {
margin-bottom: 50px;
padding: 0;
}

p.p2 {
margin-top: 25px;
padding: 0;
}

Note that we do not have paddings and borders, so none of those will contribute to the box size. As for the margins, the first paragraph has a bottom margin of 50 pixels, while the second paragraph has a top margin of 25 pixels. Does this mean that this will add up to a 75 pixel vertical margin space in between these 2 paragraphs? According to the CSS box model, the answer is: NO. Instead, the margins will collapse. Collapsing margins refers to the process of combining the margins of 2 different elements into one. In the CSS box model:
  • it is only the adjoining VERTICAL margins that have a possibility of collapsing, HORIZONTAL margins NEVER collapse
  • if both values are the same, then the collapsed margins will combine into a single margin of the same value (i.e. we do NOT add the values, two 10 pixel margins that collapse will combine into 10 pixels, not 20)
  • if both margins are positive and have different values, the collapsed margins will combine into a single margin that uses the size of the LARGER margin

So in this example, the vertical margin space between the 2 paragraphs will be 50 pixels, because that is the larger margin between the 2.

And in cases where negative margin values are involved:
  • if there is one negative margin, the absolute value of the negative margin is subtracted from the value of the non-negative margin
  • if there are 2 negative values, the collapsed margins combine into the lower value

Collapsing Margins of Parent and Child Elements


In HTML, many elements can contain other elements. In these instances, the containing element is said to be the parent element, and the element(s) inside it are its children. Let's take a look at this example:
<div>

<p>This is a paragraph inside a div.</p>

</div>
What we have here is a div element, which is a generic container element that can be used to group other elements together, mostly for CSS styling and layout purposes. Inside this div element is a paragraph element. So in this example, the div is the parent element, and the p is the child element.

There will often be instances when the margins of the parent and the child will come in contact with each other. And when that happens, the CSS box model provides rules on how to handle those instances.

Let's say we style the elements like this:

html, body {
padding: 0;
margin: 0;
}

div {
width: 200px;
height: 100px;
padding: 0;
margin: 15px;
background-color: blue;
}

p {
margin: 15px;
padding: 0;
background-color: aqua;
}

First, we see that I have styled the html and the body element to have margins and paddings of 0. The reason is that some browsers might add non-zero values to these properties, and they can interfere with the outcome of this example. So to avoid that, I am resetting their values back to 0.

Now let's look at the div's style. We see that the div has a content area of 200 by 100 pixels. As for the paragraph, we're NOT specifying width and height values. This means that the paragraph will just conform itself to the size of the div. We don't have to worry about any overflow since our paragraph has very few words, and we're not specifying exceedingly large margins, paddings, and borders.

Next, we see that the div has 0 padding on all sides and no borders. However, it does have a 15 pixel margin on each side. Later on, we'll see how its top margin will interact with the top margin of the paragraph inside it.

As for the paragraph, there are also no paddings and borders, but there is a 15 pixel margin on each side.

And lastly, for both elements, I've added background colors to better illustrate how the margins will interact with each other. Remember that an element's background color area includes the paddings, but not the margins (which are transparent).

Let's take a look at this image to better illustrate the box for each of the elements in this example. I'll show them both separately first:


The div is on the left, and the paragraph is on the right. The margins are denoted using the diagonal lines.

Next, let's show the illustration where the paragraph is inside the div. Here, we'll see that their top, left, and right margins come in contact with each other.



So how would the CSS box model treat cases like this? Well, we know that horizontal margins never collapse, so the left and the right margins for both elements will remain. As for both top margins, however, the rule of collapsing adjoining vertical margins will still apply. So the correct behavior would be to collapse both top margins like so:


This is how it would look like in a compliant browser:


Here's what's happening:
  1. The left edge of the div's content area (which is blue) is 15 pixels away from the left edge of the browser's display window.
  2. The left edge of the paragraph's content area (which is aqua) is 15 pixels away from the left edge of the div's content area.
  3. The right edge of the paragraph's content area is 15 pixels away from the right edge of the div's content area (numbers 1 to 3 behave this way, because horizontal margins do not collapse).
  4. The 15 pixel space in between the div's horizontal edges and the paragraph's horizontal edges shows the divs blue background colour. This is because the space is created by the paragraph's margin, and margins are transparent. Therefore, the div's background color will show through.
  5. Because the paragraph's top margin and the div's top margin are touching, they will collapse. The paragraph's top margin will merge into the div's top margin, so there will be no space in between the top edge of the paragraph's content area and the top edge of the div's content are (you will see that the top edge of the paragraph's aqua background aligns itself with the top edge of the div's blue background)
  6. The collapsed margin in number five will be 15 pixels, because both original top margins are 15 pixels. This collapsed margin will create a space in between the top edge of the div's content area and the top edge of the browser's display window.

The CSS box model is not always intuitive, and can often be quite confusing, so take a moment to analyze what you just read, and then let's move on to another example.

Now that you're ready to move on, let's give our previous example a slight modification. What happens if we add a border to the div element?

div {
width: 200px;
height 100px;
padding: 0;
margin: 15px;
background-color: blue;
border: 3px solid red;
}

Now that the div has a border, this means that there is something that separates the div's top margin and the paragraph's top margin. So these margins no longer come in contact with each other. And when vertical margins do NOT touch, then they will also NOT collapse. The thickness of the border does not matter. As long as the parent element has a border of any thickness, then its margins will not touch the margins of its child. So the outcome will now look like this:


So what changed:
  1. The div now has a border.
  2. There is a 15 pixel margin in between the div's border and the top of the browser's display window.
  3. There is now a 15 pixel making in between the top edge of the paragraph's content area, and the top edge of the div's content area.
  4. This new vertical area results in the div's blue background showing through that new 15 pixel space created by the paragraph's uncollapsed top margin.

I'd also like to point out that if you added a top padding to the div instead of a border, you will have the same effect. Adding padding to the div will also serve as a divider between the div's margins and the paragraph's margins. So in that case, the vertical margins will also NOT collapse. The same can be said even if you added a transparent border instead of a colored one.

At this point, I would like to end this article. There are a lot more complex examples that can illustrate how the CSS box model works, but as an introductory lesson, this will suffice. We may come across more examples in future lessons.

No comments:

Post a Comment