5

Having an inline item next to an inline-flex with a nested flex box messes with the vertical alignment (it ignores the top margin), one solution I found was to put a ::before in the inline-flex item, but I'm not really sure why this fixes it.

The first one ignores the top margin on the label, the top margin works on the second one, because of the ::before.

label {
  margin: 20px 5px 0 0;
}

.input-container {
  display: inline-flex;
}

.with-before::before {
  content: '';
}

.buttons {
  display: flex;
  flex-direction: column;
}
<div>
  <label>Top margin ignored:</label>
  <div class="input-container">
    <div class="buttons">
      <button>&lt;</button>
      <button>&gt;</button>
    </div>
    <input type="text"/>
  </div>
</div>
<br/>
<div>
  <label>Top margin works:</label>
  <div class="input-container with-before">
    <div class="buttons">
      <button>&lt;</button>
      <button>&gt;</button>
    </div>
    <input type="text"/>
  </div>
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Adding `vertical-alignment: top` and `display: inline-block` to the labels also makes the top margin work, still not sure why. https://codepen.io/GarthDB/pen/veBjvM – Garth Braithwaite Sep 11 '17 at 22:17

1 Answers1

4

The label element is inline-level by default. As such, it ignores vertical margins (and padding and height).

8.3 Margin properties: margin-top, margin-right, margin-bottom, margin-left, and margin

[margin-top and margin-bottom] have no effect on non-replaced inline elements.

However, inline-level elements are subject to the vertical-align property, and the default value is baseline, which means they align vertically per their inline content, such as images, inputs or text.

You can override this behavior with another value, such as bottom.

label {
  margin: 20px 5px 0 0;
  vertical-align: bottom;
}

.input-container {
  display: inline-flex;
}

button {
  display: flex;
  flex-direction: column;
}
<div>
  <label>Top margin ignored:</label>
  <div class="input-container">
    <div class="buttons">
      <button>&lt;</button>
      <button>&gt;</button>
    </div>
    <input type="text" />
  </div>
</div>
<br/>
<div>
  <label>Top margin works:</label>
  <div class="input-container with-before">
    <div class="buttons">
      <button>&lt;</button>
      <button>&gt;</button>
    </div>
    <input type="text" />
  </div>
</div>

As to how the ::before pseudo-element "fixes" the problem, here are two comments / observations:

  1. Adding the ::before pseudo-element does not get the top margin to work. If you remove that margin, you'll see that the label is still aligned to the bottom with the pseudo.

  2. I think the pseudo – when empty – shifts the label down because it establishes a new baseline for the container. However, if you add any content to it (I just tried content: "x"), the label shifts right back to the top.

I'm pretty sure this is all related to a shifting baseline. Again, just override the baseline value in vertical-align with bottom, middle or top.

More information:

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701