CSS Right Margin Does Not Work Inside a Div With Overflow Scroll

TL;DR:

Margins are for moving an element in from the wrapper, not expanding the wrapper outwards.

The long explanation:

This behavior is consistent with specifying a width in addition to a horizontal margin anywhere in the document. To break it down, consider the following snippet, where I specificity a wrapper without an overflow property, and the margin does not expand the wrapper element.

body {
    padding: 20px;
}
.outer {
    width: 400px;
    border: 1px solid black;
}
.inner {
    width: 400px;
    height: 40px;
    margin: 0 20px;
    background: grey;
}
<div class="outer">
    <div class="inner">
        
    </div>
</div>

As you can see, the margin did not cause the wrapper to expand in size, the element just continued to overflow. This behavior is documented under Visual formatting model details of the is documented in the CSS 2.1 specification.

Excerpt from the "Block-level, non-replaced elements in normal flow" section of "Visual formatting model details":

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

[...]

If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl', this happens to 'margin-left' instead.

That excerpt is quite dense, so to simply, let's ignore the width of border and padding, both of which are 0, leaving us with width, margin-left, and margin-right.

Now since the you have a fixed width and values for margin-left and margin-right, the values are "over-constrained". Now in our example, since the direction is ltr by default, the margin-right is forced to compensate.

To see the effects of the direction, let's try adding a dir="rtl" attribute to the wrapper element.

body {
    padding: 20px;
}
.outer {
    width: 400px;
    border: 1px solid black;
}
.inner {
    width: 400px;
    height: 40px;
    margin: 0 20px;
    background: grey;
}
<div class="outer" dir="rtl">
    <div class="inner">
        
    </div>
</div>

Now the element is overflowing to the left. Let's see if this dir="rtl" attribute has the same effect on your overflow: scroll example.

#outer {
    border: 1px solid #00F;
    width: 200px;
    height: 100px;
    overflow: scroll;
}
#inner {
    border: 1px solid #F0F;
    margin: 25px;
    width: 400px;
    height: 200px;
}
<div id="outer" dir="rtl">
    <div id="inner">
    
    </div>
</div>

Yep, it does. The margin is now missing on the left, rather than the right.

But why doesn't overflow: scroll include the margins?

Mainly because the specification does not say it should. Let's take a look at the CSS 2 specification for the overflow property.

Excerpt from the "Overflow and clipping" section of "Visual effects":

Whenever overflow occurs, the 'overflow' property specifies whether a box is clipped to its padding edge, and if so, whether a scrolling mechanism is provided to access any clipped out content.

See how it specifically says "clipped out content". For an explanation of "content", lets refer to the following graphic from the CSS 2 specification.

Graphic from the "Box dimensions" section of the "Box model":

box model

As we can see, the margin is separate from the content. However, at this point it's worth noting that padding and borders are included in the scrolling area, so when the spec says "content", it is likely referring to border-box, or at least, that seems to be how it was interpreted.

Why does display: inline-block work?

Basically, margins behave differently on inline-block elements, because they are content level rather than block level, and they do not have a concept of being "over-constrained".


add display:inline-block; to #inner div

see this fiddle


So the answers here don't actually solve the problem! (Although super detailed to the reason why it doesn't work)

I needed a solution. Here's mine for future readers. Use a combination of display:flex; with pseudo ::after element to fake the presence of a div to provide the margin needed.

.wrapper {
  display: flex;
  width: 400px;
  height: 100%;
  padding: 40px;
  background: lightGrey;
}

.lists_container {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  overflow: auto;
  position: relative;
  background: grey;
  padding: 40px;
  margin: 40px;
  width: 100%;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 250px;
  max-width: 500px;
  height: 100px;
  margin: 50px 0;
  padding: 20px;
  background: orange;
  margin-right: 30px;
}

.card.last::after {
  content: '';
  position: absolute;
  right: -100px;
  width: 40px;
  height: 100%;
  background: red;
}
<div class="wrapper">

  <div class="lists_container">

    <div class="card">
    </div>

    <div class="card">
    </div>

    <div class="card last">
    </div>

  </div>

</div>