CSS newbie TOP question: Adding padding in exercise creates large gap on top of element. Adding margin-top of 0 fixes it but I don't know why

Viewed 47

This is my first question on stackoverflow, so please bear with me if I don't do everything correct. If I can format this better, please let me know.

I am working through the TOP 2nd CSS Margin/Padding exercises. I was able to get through the first one no problem, but I have a situation that I don't understand in the second task.

The goal was to manipulate the padding/margins to achieve a certain desired outcome. Below is the original HTML and the CSS original, followed by the solution. I've put a link to the .png of desired outcome at the bottom.

My question is specifically about the .card and .title elements.

Before the 8px padding was added to the .card element, the edge of the blue background inside the .title element when right up to the top edge of the box and were flush with the .card element. When I add 8px padding to the .card element, it seems to add it correctly to the left, right and bottom of everything, however the top of the .title element seems almost double in white space between the top of the blue box in the .title element and the top of the .card element.

This is fixed then by adding the margin-top: 0; in the .title element.

I'm having a very hard time conceptualizing why I need to add the margin-top of 0. I think I understand everything else. But why is everything flush without the padding, but when I add the 8px padding, it looks good on all sides except the top which appears double, necessitating the margin-top: 0; being inserted into the .title element

Does it have anything to do with an h1 margin having some extra margin to begin with? Again, this is my first run at CSS so I'm not sure if that is correct. If it does have something to do with the h1 margin, why am I only seeing it when I add the padding?

Perhaps I'm missing a super easy concept here, but it's doing my head in so any help would be appreciated.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Margin and Padding exercise 2</title>
    <link rel="stylesheet" href="solution.css">
  </head>
  <body>
    <div class="card">
      <h1 class="title">I'm a card</h1>
      <div class="content">I have content inside me..lorem ipsum blah blah blah. Here's some stuff you need to read.</div>
      <div class="button-container">and a <button>BIG BUTTON</button></div>
    </div>
  </body>
</html>

CSS Original + Solution

body {
      background: #eee;
      font-family: sans-serif;
    }
    
    .card {
      width: 400px;
      background: #fff;
      margin: 16px auto;
    }
    
    .title {
      background: #e3f4ff;
    }
    
    .content {
      background: #e3f4ff;
    }
    
    .button-container {
      background: #e3f4ff;
    }
    
    button {
      background: white;
      border: 1px solid #eee;
    }
    
    /* SOLUTION */
    
    /* disclaimer: duplicating the selectors here isn't best practice.
    In your solution you probably put it right inside the existing selectors,
    which _is_ the best practice.
    We separated it out here to make it extra clear what has changed. */
    
    .card {
      padding: 8px;
    }
    
    .title {
      margin-top: 0;
      margin-bottom: 8px;
      font-size: 16px;
      padding: 8px;
    }
    
    .content {
      margin-bottom: 8px;
      padding: 16px 8px;
    }
    
    .button-container {
      text-align: center;
      padding: 8px;
    }
    
    button {
      display: block;
      margin: 0 auto;
      padding: 8px 24px;
    }

body {
  background: #eee;
  font-family: sans-serif;
}

.card {
  width: 400px;
  background: #fff;
  margin: 16px auto;
}

.title {
  background: #e3f4ff;
}

.content {
  background: #e3f4ff;
}

.button-container {
  background: #e3f4ff;
}

button {
  background: white;
  border: 1px solid #eee;
}


/* SOLUTION */


/* disclaimer: duplicating the selectors here isn't best practice.
        In your solution you probably put it right inside the existing selectors,
        which _is_ the best practice.
        We separated it out here to make it extra clear what has changed. */

.card {
  padding: 8px;
}

.title {
  margin-top: 0;
  margin-bottom: 8px;
  font-size: 16px;
  padding: 8px;
}

.content {
  margin-bottom: 8px;
  padding: 16px 8px;
}

.button-container {
  text-align: center;
  padding: 8px;
}

button {
  display: block;
  margin: 0 auto;
  padding: 8px 24px;
}
<div class="card">
  <h1 class="title">I'm a card</h1>
  <div class="content">I have content inside me..lorem ipsum blah blah blah. Here's some stuff you need to read.</div>
  <div class="button-container">and a <button>BIG BUTTON</button></div>
</div>

enter image description here

3 Answers

The reason for the phenomenon you're observing is a CSS "feature" called collapsing margins, which has been giving developers headaches for literally decades.

Let me show you a very simplified example of how it works.

.outer {
  background-color: green;
  height: 250px;
}

.inner {
  background-color: orange;
  height: 100px;
}
<div class="outer">
  <div class="inner"></div>
</div>

So this does what we're expecting it to do: It shows the orange div.inner right inside the green div.outer, at the very top of div.outer.

So what if we want to move the div.inner like let's say 20px down inside div.outer?

Let's try what seems intuitive: .inner { margin-top: 20px; }

.outer {
  background-color: green;
  height: 250px;
}

.inner {
  background-color: orange;
  height: 100px;
  /* let's move it down 20px */
  margin-top: 20px;
}
<div class="outer">
  <div class="inner"></div>
</div>

Now instead of moving down div.inner inside div.outer, the whole div.outer has moved, with the div.inner still at the very same position relative to div.outer.

Huh???

This is where collapsing margins kick in. In certain conditions, if a parent with a margin-top (0 by default for div) has a child that has a margin-top (like in your code the h1 has), both margins collapse, meaning whichever element has the greater margin is applied to the parent element, not the child.

This only applies as long as the parent element has no padding-top set. Simply setting that to 1px stops margins from collapsing:

.outer {
  background-color: green;
  height: 250px;
  /* stops collapsing margins: */
  padding-top: 1px;
}

.inner {
  background-color: orange;
  height: 100px;
  /* let's move it down 20px */
  margin-top: 20px;
}
<div class="outer">
  <div class="inner"></div>
</div>

What is going on is described at MDN for three different basic cases, this one applying here:

No content separating parent and descendants

If there is no border, padding, inline part, block formatting context created, or clearance to separate the margin-top of a block from the margin-top of one or more of its descendant blocks; or no border, padding, inline content, height, or min-height to separate the margin-bottom of a block from the margin-bottom of one or more of its descendant blocks, then those margins collapse. The collapsed margin ends up outside the parent.

You are correct, h1 has an inherent margin associated with it. I believe in chrome it is 0.67em. You can demonstrate this property by simply changing the h1 in <h1 class="title">I'm a card</h1> to a div and you can see how there's no margin anymore when you apply this.

Below in this example all I did was remove the margin-top: 0; from .title and switched h1 to divand you can see there no margin anymore

body {
      background: #eee;
      font-family: sans-serif;
    }
    
    .card {
      width: 400px;
      background: #fff;
      margin: 16px auto;
    }
    
    .title {
      background: #e3f4ff;
    }
    
    .content {
      background: #e3f4ff;
    }
    
    .button-container {
      background: #e3f4ff;
    }
    
    button {
      background: white;
      border: 1px solid #eee;
    }
    
    /* SOLUTION */
    
    /* disclaimer: duplicating the selectors here isn't best practice.
    In your solution you probably put it right inside the existing selectors,
    which _is_ the best practice.
    We separated it out here to make it extra clear what has changed. */
    
    .card {
      padding: 8px;
    }
    
    .title {
      margin-bottom: 8px;
      font-size: 16px;
      padding: 8px;
    }
    
    .content {
      margin-bottom: 8px;
      padding: 16px 8px;
    }
    
    .button-container {
      text-align: center;
      padding: 8px;
    }
    
    button {
      display: block;
      margin: 0 auto;
      padding: 8px 24px;
    }
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Margin and Padding exercise 2</title>
    <link rel="stylesheet" href="solution.css">
  </head>
  <body>
    <div class="card">
      <div class="title">I'm a card</div>
      <div class="content">I have content inside me..lorem ipsum blah blah blah. Here's some stuff you need to read.</div>
      <div class="button-container">and a <button>BIG BUTTON</button></div>
    </div>
  </body>
</html>

You're absolutely right about the margin on the h1 tag. Browsers add default styling to most HTML elements. It varies slightly between browsers, but in Chrome by default an h1 tag has about 0.67em of margin above and beneath it.

These default stylings are included to aid legibility of HTML documents that don't have any CSS applied – but they can all be overridden.

A really handy feature to take advantage of when you're writing CSS is your browser's 'Inspect element' feature: If you right click on your h1 tag in your browser and click 'Inspect element` in the menu that appears, you can see both the styling you've applied and the browser's default styling, referred to as the 'user agent stylesheet.'

If you hover over an element you can see how its padding and margin are affecting its layout.

enter image description here

You can see Chrome by default adds a margin-block-start and margin-block-end to the h1 tag by default. It's worth asking why it doesn't just use margin-top and margin-bottom, but the margin-block property covers off text that isn't oriented from left to right, or is rotated. Either way, setting your own margin-top and margin-bottom will override it, as you've done.

@connexo has described the collapsing margins phenomenon, which of course adds even more to ponder. This Medium article provides a little more context on why it occurs, using paragraphs as an example.

Related