Putting a flexbox inside of a list item is causing the content to get pushed down by what appears to be a full line height.
I've been playing around with different CSS properties, such as giving the flexbox margin-top: 0
, its children margin-top:0
, adding flex-wrap
to the flexbox, etc. No dice!
.wrapper {
display: flex;
}
li {
background: #ccc;
}
<ul>
<li>
<div class="wrapper">
<div>hello</div>
<div>world</div>
</div>
</li>
</ul>
codepen
This seems due to the ::marker
pseudo-element.
I don't know exactly what should happen because this is an overlap of various specs
list-item
keyword::marker
pseudo-elementAnd it's problematic because they are proposals not ready for implementation. See for example the warning in CSS Lists 3 - Positioning Markers, which is also relevant in this issue:
This section is not ready for implementation. It is an unreviewed rough draft that hasn’t even been properly checked for Web-compatibility. Feel free to send feedback, but DON’T USE THIS PART OF THE SPEC.
The following is my guess of how Chrome handles markers under the hood.
First, let's use the simpler list-style-position: inside
on the list item, and let it contain a block:
ul {
list-style-position: inside;
}
li {
border: 1px solid blue;
}
div {
border: 1px solid red;
}
<ul>
<li>
<div>Hello<br />world</div>
</li>
</ul>
Chrome displays it like this:
Note the marker overlaps the div, but shrinks its first line box. This behavior is typical of floats!
A float should not overlap a formatting context root. For example, display: inline-block
establishes a block formatting context.
ul {
list-style-position: inside;
}
li {
border: 1px solid blue;
}
div {
display: inline-block;
border: 1px solid red;
}
<ul>
<li>
<div>Hello<br />world</div>
</li>
</ul>
So like a float, the marker pushes it to the right, to prevent overlapping.
But note the marker is vertically aligned. And establishing a block formatting context with overflow: hidden
does not prevent overlapping.
ul {
list-style-position: inside;
}
li {
border: 1px solid blue;
}
div {
overflow: hidden;
border: 1px solid red;
}
<ul>
<li>
<div>Hello<br />world</div>
</li>
</ul>
So it has some float-like behaviors, but it's not a float.
My guess is that Chrome keeps iterating the first box in the list item, then the first box inside it, and so on, until the first box is no longer a block. Then it inserts the marker just before it, displayed inline-level (maybe inline-block
). Something like this:
var deepestFirstBlock,
child = listItem;
do {
deepestFirstBlock = child;
child = deepestFirstBlock.firstBoxChild;
} while (child.style.display == 'block');
deepestFirstBlock.insertBefore(marker, deepestFirstBlock.firstBoxChild);
It's not exactly that, because if the direction
of the deepest first block is right-to-left, then the marker is inserted after the last box of the first line.
Therefore, as noticed by Michael_B, when you style the inner container with display: inline-flex
there is no empty space. That's because the marker is inserted before it, and since both are inline-level they appear side by side.
ul {
list-style-position: inside;
}
li {
border: 1px solid blue;
}
div {
display: inline-flex;
border: 1px solid red;
}
<ul>
<li>
<div>Hello<br />world</div>
</li>
</ul>
But a display: flex
container is block-level. So it is forced to go to the next line. The empty space is then the marker, which occupies the first line box.
ul {
list-style-position: inside;
}
li {
border: 1px solid blue;
}
div {
display: flex;
border: 1px solid red;
}
<ul>
<li>
<div>Hello<br />world</div>
</li>
</ul>
This does not happen with display: block
, which is also block-level, because the marker is inserted inside it. I guess the marker is not inserted inside a block-level flex container because then it would become a flex item and it could produce a bizarre layout.
When you use the initial list-style-position: outside
, it seems Chrome just applies some kind of negative horizontal margin to the marker, so that the following box (which was the first one before inserting the marker) becomes aligned at the left of the container like before inserting the marker. Maybe the marker is also floated, since floated contents inside the list-item are no longer able to appear at its left.
However, this negative margin won't allow the block-level flex container to go up. That's why the space remains, even though the marker is no longer in there.
Note this contradicts the current unreviewed rough draft not ready for implementation, which says markers with list-style-position: outside
should have position: marker
, and
An element with
position: marker
counts as absolutely positioned.
Absolutely positioned elements are taken out-of-flow and overlap following elements, so the flex container should me moved to the top.
Firefox seems to do something simpler. With list-style-position: inside
, it just inserts the marker as the first (inline-level) box of the list-item. It doesn't attempt to iterate blocks to insert the marker at the deepest possible level.
However, with list-style-position: outside
, it probably takes the marker out-of-flow, just like the draft says. That's why you don't see the space in Firefox.
If you switch .wrapper
from display: flex
to inline-flex
, the issue is resolved (demo).
If you set list-style-type
to none
, the issue is resolved (credit @Ricky_Ruiz in the comments) (demo).
The problem exists in Chrome, Edge and IE11. The original code works fine in Firefox and Safari.
I'm currently not sure what's causing the behavior.
For some reason (apparently the marker), the top section of the li
is off-limits. The parent is not allowing the children to access the area.
(UPDATE: A detailed explanation has been provided in another answer.)
In testing...
padding
and margin
from ul
and descendants. It didn't help.line-height
and vertical-align
. Also didn't help.By setting an equal height to all elements, it's clear that something is forcing the flex container to render lower than expected, and overflow the parent.
ul {
padding: 0;
margin: 0;
height: 100px;
border: 1px solid black;
}
li {
padding: 0;
margin: 0;
background: #ccc;
height: 100%;
}
.wrapper {
display: flex;
padding: 0;
margin: 0;
height: 100%;
border: 1px dashed red;
}
<ul>
<li>
<div class="wrapper">
<div>hello</div>
<div>world</div>
</div>
</li>
</ul>
revised codepen
Adding display: block
to the li
style properties resolves this. By default, the user agent styles will set li
elements to display as list-item
. The flexbox specifications do not account for this, instead specifying behavior for only block, inline, and inline-block elements (as well as table-cell elements, which trigger block-level flex items).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With