Vince
Vince

Reputation: 4232

flexbox: margin affects baseline?

Why does setting margin to auto break flexbox's baseline alignment?

I want to align a heading with what I'll call accent text according to the baseline. It works fine using display: flex and align-items: baseline on the container.

I also want to remove the margin on the left side of the heading, set the margin on the right side of the heading to a specific value, and leave the margins at the top and bottom of the heading to the default (user-agent stylesheet) value. For example, h1 { margin: auto 1em auto 0; } This is where it breaks and the item alignment appears to revert to the default of stretch.

Now, developer tools shows me that when I set the top and bottom values of the margin to auto, it actually ends up with a margin of 0 instead of what I expected. However, when I actually set the top and bottom margins to 0 (e.g.: margin: 0 1em 0 0), the problem doesn't occur.

I've already seen how to work around this problem by just setting margin-left and margin-right individually without using the shorthand property, but I'm hoping to get a better understanding of why this is happening.

header {
    background-color: #ffd;
    display: flex;
    justify-content: flex-start;
    align-items: baseline;
    margin: 0 1em 0 0;
}

h1 {
    margin: auto 1em auto 0;
}
<header>
  <h1>Heading</h1>
  <div class="accent">Accent Text</div>
</header>

Upvotes: 3

Views: 1532

Answers (3)

Michael Benjamin
Michael Benjamin

Reputation: 371799

The first thing to note is that align-items on the flex container is ignored by flex items having an auto margin set in the cross axis. (justify-content would be ignored by flex items with auto margins set in the main axis).

8.1. Aligning with auto margins

If free space is distributed to auto margins, the [keyword] alignment properties [such as align-items and justify-content] will have no effect in that dimension because the margins will have stolen all the free space left over after flexing.


The second thing to note is that the align-items property sets the default align-self on flex items. align-items: baseline means align-self: baseline on all flex items, unless you override align-self for a particular item.

8.3. Cross-axis Alignment: the align-items and align-self properties

Flex items can be aligned in the cross axis of the current line of the flex container, similar to justify-content but in the perpendicular direction.

align-items sets the default alignment for all of the flex container's items, including anonymous flex items.

align-self allows this default alignment to be overridden for individual flex items.

For an example of an align-self override see this post:


In terms of your code...

header {
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  margin: 0 1em 0 0;
  background-color: #ffd;  
}

h1 {
  margin: auto 1em auto 0;
}
<header>
  <h1>Heading</h1>
  <div class="accent">Accent Text</div>
</header>

... here's what's happening:

  • align-items: baseline / align-self: baseline are ignored by the h1 because it has auto margins in the cross axis.
  • align-items: baseline / align-self: baseline is respected by the div because it has no auto margins.

The question then becomes, why is the div aligned to the top of the container?

Because in a group of flex items with baseline alignment, the item with the largest distance between the container's cross-start edge (the top edge, in this case) and the item's cross-start margin (the top margin, in this case), is placed flush against the cross-start edge.

Since there is only one item with baseline alignment in the container, it shoots straight to the top.

8.3. Cross-axis Alignment: the align-items and align-self properties

baseline

The flex item participates in baseline alignment: all participating flex items on the line are aligned such that their baselines align, and the item with the largest distance between its baseline and its cross-start margin edge is placed flush against the cross-start edge of the line.

For a clear illustration of this behavior see this post:

Upvotes: 2

Lee A.
Lee A.

Reputation: 33

Margin: auto; 

interacts in a very special way with flexbox. What it does is take all remaining empty space on its axis and adds it to the element.

Your problem is coming from the fact that you setting both margin-top and margin-bottom to auto. Doing so causes all of the empty space to be allocated evenly between the top and bottom margins of the element.

This is also why margin: auto will perfectly center an element in flexbox.

You can use this jsfiddle to see more how the margin:auto works with flexbox.

https://jsfiddle.net/kaoLkpgz/2/

Replace any of the margin: 0; values of .child with auto and you can see how each interacts with the others.

Upvotes: 0

Asons
Asons

Reputation: 87251

auto margin's override the align-items/justify-content property set on the flex container.

So what happens is, the default top/bottom margin will be removed, and then the h1 will be vertically centered in its parent, instead of aligned at the baseline.

If you give the header a height, as I did in this fiddle demo, you'll see what goes on.

To keep the default top/bottom value, only adjust its left/right value.

Stack snippet

header {
    background-color: #ffd;
    display: flex;
    justify-content: flex-start;
    align-items: baseline;
    margin: 0 1em 0 0;
}

h1 {
    margin-right: 1em;             /*  changed  */
    margin-left: 0;                /*  changed  */
}
<header>
  <h1>Heading</h1>
  <div class="accent">Accent Text</div>
</header>

Upvotes: 3

Related Questions