3

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>
Asons
  • 84,923
  • 12
  • 110
  • 165
Vince
  • 3,962
  • 3
  • 33
  • 58

3 Answers3

3

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>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • and there is no way to use margin:auto and also force the justify-content/align-items to be consider ? something like using important ? – Temani Afif Feb 13 '18 at 23:10
  • @TemaniAfif No, using `!important` will only affect the same property it is set on, not disable another property's capabilities to override one. – Asons Feb 13 '18 at 23:19
  • no i didn't meant using !important :) i mean is there any thing in this situation that can be used to force align-items/justify-content to be consider even with margin auto ? [i used important to illustrate how this works with normale CSS when it's the same property] – Temani Afif Feb 13 '18 at 23:21
  • @TemaniAfif Aha :) ... still no, as e.g. in a flex row container there is no `justify-self` that one can use to override `justify-content`, hence the need for auto margin to be able to. – Asons Feb 13 '18 at 23:28
  • 2
    @TemaniAfif, you wrote: *"...there is no way to use margin:auto and also force the justify-content/align-items to be consider?..."* and *"...i mean is there any thing in this situation that can be used to force align-items/justify-content to be consider even with margin auto?"* ... It's not a matter of forcing the keyword alignment properties to work with `margin: auto`... – Michael Benjamin Mar 08 '18 at 18:34
  • 2
    When you apply an auto margin to a flex item it consumes all available space. Hence, the `justify-content` and `align-*` properties, whose function is to distribute free space in the container, a rendered useless. There is no free space left for them to use. – Michael Benjamin Mar 08 '18 at 18:35
2

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:

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
0
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.

Lee A.
  • 33
  • 6
  • not sure this is the explanation ... there is no space for margin, so auto and 0 should behave the same – Temani Afif Feb 13 '18 at 22:37
  • It's not very obvious unless you add extra space to the container, but that's just how flexbox handles margin. Once you understand how it does it, it's great, but it can be pretty confusing at first. Also adding in the justify/align items style shows how it works in more detail: https://jsfiddle.net/kaoLkpgz/2/ – Lee A. Feb 13 '18 at 22:56
  • am not the OP :) and it's not a question of obvious or not ... you are explaining that margin are allocated evenly BUT in this case there is no space to allocate any margin so using auto or 0 should provide the same result ... but check the other answer to understand that margin auto break another property ;) – Temani Afif Feb 13 '18 at 22:58
  • I am aware that you are not the OP. I never addressed you as such. And margin: auto; doesn't break the other styling. it simply gets applied on top of it. If it didn't, you would be unable to add margin to elements in flex layouts. Also, there ***is*** extra space. If there wasn't, align-items: baseline would not work. Align-items: baseline puts all remaining whitespace above the element. – Lee A. Feb 13 '18 at 23:01
  • check the other answer and you will understand :) margin auto override another property .. and it has nothing to do with the way it works like you explained – Temani Afif Feb 13 '18 at 23:02
  • ok nevermind :) we won't understand each other ;) .. by the way you can take a look to the other answer. – Temani Afif Feb 13 '18 at 23:09
  • @LGSon, not sure why bringing up downvotes is necessary. On topic: I was oversimplifying the process for the sake of the example, and I've retracted that comment because you are correct, it's not specifically true. I simply meant to illustrate the theory behind what 'align-items:baseline' does. In practice, what it does is adds any existing margins to the element, then puts all **remaining** empty space above the element. Looking at it that way, it's pretty clear why they don't function together very well, because margin:auto doesn't have any empty space left over. – Lee A. Feb 14 '18 at 14:20
  • @LeeA. Great...and I get what you meant, just wanted to make sure we provide users with a proper wording. I will delete my comments too, this included, as we now understand each other perfectly fine. When you read this, give it an upvote so I'll know and can delete it. – Asons Feb 14 '18 at 14:48