CSS Flexbox with position absolute confusion - css

I'm confused how this works could someone walk me through what exactly happened?
body,
html {
padding: 0;
margin: 0;
height: 100vh;
width: 100%;
}
.chat-break {
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.chat-break .line {
border-bottom: 1px solid #ddd;
width: 100%;
}
.chat-break .date {
color: #B5B5B5;
position: absolute;
background-color: #fff;
padding-left: 8px;
padding-right: 8px;
}
<div class="chat-break">
<div class="line">
</div>
<p class="date">Today</p>
</div>
My understanding:
.chat-break flexbox has two elements .line and .date.
...but after using position: absolute on .date its no longer its own element inside the flexbox?
Then why is "Today" being centered on the line?

Preface:
The top, right, bottom, and left properties are collectively referred to as "box inset" properties.
Keep in mind that all CSS properties that aren't explicitly set will have default values that are either "initial" values or inherited values.
So unless explicitly specified otherwise, every element has position: static;.
The initial value for all of the box-inset properties is auto, regardless of their element's position and display property.
But the behaviour of the auto value does depend on the element's position and/or formatting context.
CSS has a lot of surprising and counter-intuitive behaviour.
This is not intentional, but a consequence of how CSS evolved over time over the past ~25 years it's existed.
Originally CSS wasn't capable of any layout control at all, it was only useful for things like font and text styling.
In fact, CSS's main layout features (like flex, grid, etc) weren't added (let alone supported by browsers) until well into the 2010s.
For example, the original 2008 spec for flex was abandoned. It was reinvented in 2011, and not widely supported until 2014.
Why <div class="line"> fills the width of the page:
<div class="chat-break"> has display: flex;, which makes it a flex-parent.
All immediate child elements (with certain exceptions) of a flex-parent, that have position: static, are flex-items.
Therefore<div class="line"> is a flex-item.
Therefore<p class="date"> is not a flex-item because it has position: absolute;.
(Generally speaking) elements with position: absolute; are not subject to their layout-container's layout rules and are re-positioned with absolute-coordinates where the origin is in their-closet-ancestor-element-without-position: static; (yes, that's a mouthful).
This is why position: relative; is being applied to <div class="chat-break">.
Therefore <div class="chat-break"> is a flex-parent with only one flex-item, despite having two element children.
And because it has justify-content: center; it means that its single flex-item (<div class="line">) will be centered.
Also, because <div class="chat-break"> has display: flex; (rather than display: inline-flex) it means that <div class="chat-break"> is a block-level element, so it fills the width of its container, which is <body>, which fills the width of the viewport.
And because <div class="line"> also has width: 100%; (which becomes flex-basis: 100%;) it means the <div class="line"> will fill the width of <div class="chat-break">.
Therefore <body>, <div class="chat-break">, and <div class="line"> (in that order) will fill the width of the viewport.
Why <p class="date"> is centered:
As <p class="date"> uses auto for all its box inset properties (i.e. top, right, etc) with position: absolute; then the computed value of those properties is the same as if <p class="date"> was position: static;.
However, if <p class="date"> was position: static; then it would be a flex-item and would share its flex row with <div class="line"> - which would mean it would be located somewhere off to the right of the line (due to justify-content: center;).
But it's actually centered, because this is a special-case scenario that's specifically specified in the specification...
https://www.w3.org/TR/css-flexbox-1/#abspos-items
4.1. Absolutely-Positioned Flex Children
As it is out-of-flow, an absolutely-positioned child of a flex container does not participate in flex layout.
The static position of an absolutely-positioned child of a flex container is determined such that the child is positioned as if it were the sole flex item in the flex container, assuming both the child and the flex container were fixed-size boxes of their used size. For this purpose, auto margins are treated as zero.
It's well-worth reading the rest of the section as it also covers other brain-stretching scenarios, like "what if align-self: center; is used"?

Related

CSS height mysteries: Different on Chrome and Firefox

This codepen looks different on Chrome than on Firefox. The html and css is:
<div class="outer">
<div>header</div>
<div class="grid">
<div class="central">central</div>
</div>
</div>
.outer {
min-height: 200px;
background: lightsalmon;
display: flex;
flex-direction: column;
}
.grid {
display: grid;
flex: 1 1 0%;
}
.central {
/*height: 98%;*/
background: lightcoral;
}
On Chrome, even though the central element's height is always relative to the intermediate elements actual height (when placed with grid or when using a percentage height like the one in comments), but only if is grandparent's height was explicitly set (and even though that height is not what the central element's is calculated in relation to).
(Grid layout seems to have no such issue: If you change the outer element's display from flex to grid it works on Chrome too.)
Is this a bug or is the behaviour not clearly enough defined?
I can't say I understand that I understand's Mozilla's formal definition of percentage heights on this page anyway and I don't know if there's a more rigorous definition.

CSS Grid auto rows height issue in Firefox ESR [duplicate]

I want to have a square div inside a flexbox. So I use:
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
padding-bottom: 50%;
}
<div class="outer">
<div class="inner">
<a>hehe</a>
</div>
</div>
This works fine in Chrome. But in Firefox, the parent squeezes to just one line.
How do I solve this in Firefox? I use version 44.
You can also view the code at https://jsbin.com/lakoxi/edit?html,css
2018 Update
The flexbox specification has been updated.
4.2. Flex Item Margins and Paddings
Percentage margins and paddings on flex items, like those on block
boxes, are resolved against the inline size of their containing block,
e.g. left/right/top/bottom percentages all resolve against their
containing block’s width in horizontal writing modes.
Original Answer - applies to FF and Edge versions released before 2018
From the flexbox specification:
Authors should avoid using percentages in paddings or margins on flex items entirely, as they will get different behavior in different browsers.
Here's some more:
4.2. Flex Item Margins and Paddings
Percentage margins and paddings on flex items can be resolved against either:
their own axis (left/right percentages resolve against width, top/bottom resolve against height), or,
the inline axis (left/right/top/bottom percentages all resolve against width)
A User Agent must choose one of these two behaviors.
Note: This variance sucks, but it accurately captures the current state of the world (no consensus among implementations, and no consensus within the CSSWG). It is the CSSWG’s intention that browsers will converge on one of the behaviors, at which time the spec will be amended.
In addition to Michael_B's answer, here is a possible workaround.
When using percent we often relate that to the viewport width, so with that in mind, viewport units vw/vh can be an option, since it works similar (responsive).
Stack snippet
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
padding-bottom: 50vw;
}
<div class="outer">
<div class="inner">
<a>hehe</a>
</div>
</div>
Updated based on a comment
If a square is a must, and viewport units or script can't be used, here is another trick using a dummy image.
Note, as image also a SVG or a Base64 could be used as a datauri to save an extra round trip to the server
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
}
.inner img {
display: block;
width: 100%;
visibility: hidden;
}
<div class="outer">
<div class="inner">
<img src="http://placehold.it/10" alt="">
</div>
</div>

Maintaining height of element relative to its width [duplicate]

I want to have a square div inside a flexbox. So I use:
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
padding-bottom: 50%;
}
<div class="outer">
<div class="inner">
<a>hehe</a>
</div>
</div>
This works fine in Chrome. But in Firefox, the parent squeezes to just one line.
How do I solve this in Firefox? I use version 44.
You can also view the code at https://jsbin.com/lakoxi/edit?html,css
2018 Update
The flexbox specification has been updated.
4.2. Flex Item Margins and Paddings
Percentage margins and paddings on flex items, like those on block
boxes, are resolved against the inline size of their containing block,
e.g. left/right/top/bottom percentages all resolve against their
containing block’s width in horizontal writing modes.
Original Answer - applies to FF and Edge versions released before 2018
From the flexbox specification:
Authors should avoid using percentages in paddings or margins on flex items entirely, as they will get different behavior in different browsers.
Here's some more:
4.2. Flex Item Margins and Paddings
Percentage margins and paddings on flex items can be resolved against either:
their own axis (left/right percentages resolve against width, top/bottom resolve against height), or,
the inline axis (left/right/top/bottom percentages all resolve against width)
A User Agent must choose one of these two behaviors.
Note: This variance sucks, but it accurately captures the current state of the world (no consensus among implementations, and no consensus within the CSSWG). It is the CSSWG’s intention that browsers will converge on one of the behaviors, at which time the spec will be amended.
In addition to Michael_B's answer, here is a possible workaround.
When using percent we often relate that to the viewport width, so with that in mind, viewport units vw/vh can be an option, since it works similar (responsive).
Stack snippet
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
padding-bottom: 50vw;
}
<div class="outer">
<div class="inner">
<a>hehe</a>
</div>
</div>
Updated based on a comment
If a square is a must, and viewport units or script can't be used, here is another trick using a dummy image.
Note, as image also a SVG or a Base64 could be used as a datauri to save an extra round trip to the server
.outer {
display: flex;
width: 100%;
background: blue;
}
.inner {
width: 50%;
background: yellow;
}
.inner img {
display: block;
width: 100%;
visibility: hidden;
}
<div class="outer">
<div class="inner">
<img src="http://placehold.it/10" alt="">
</div>
</div>

In flexbox, why do we define the container and not the elements themselves?

I'm trying to understand the flex property.
Why do we apply float and display: inline-block to the elements that are nested inside a container, while with display: flex, we define the container itself?
How can the elements inside be display:block and still be positioned on the same row?
.flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-direction: row;
}
#media (max-width: 600px) {
.flex {
flex-direction: column;
}
}
#aaa {
border: 3px solid black;
flex: 2;
}
#aa {
border: 3px solid black;
flex: 1;
}
#a {
border: 3px solid black;
flex: 1;
}
#bbb {
width: 300px;
}
#ccc {
float: left;
border: 5px solid yellow;
width: 200px;
}
#ddd {
clear: both;
}
.one {
background: red;
height: 50px
}
.two {
background: green;
height: 50px
}
.tree {
background: blue;
height: 50px
}
#eee {
display: inline-block;
width: 200px;
}
<div class=flex>
<div id=a class="one">flex</div>
<div id=aa class="two">flex</div>
<div id=aaa class="tree">flex</div>
</div>
<div class=block>
<div id=bbb class="one">block</div>
<div id=bbb class="two">block</div>
<div id=bbb class="tree">block</div>
</div>
<div class=float>
<div id=ccc class="one">float</div>
<div id=ccc class="two">float</div>
<div id=ccc class="tree">float</div>
</div>
<div class=block>
<div id=ddd class="one">block</div>
<div id=ddd class="two">block</div>
<div id=ddd class="tree">block</div>
</div>
<div class=block>
<div id=eee class="one">block</div>
<div id=eee class="two">block</div>
<div id=eee class="tree">block</div>
</div>
CodePen demo
In flexbox, why do we define the container and not the elements themselves?
The reason is twofold:
Flex containers are the only things flex items can appear as children of, since only flex containers generate flex layout.
Unlike anonymous block boxes and anonymous table boxes, there are no such things as anonymous flex container boxes.
So, a hypothetical display: flex-item wouldn't work unless the element's parent was a flex container, and since every child of a flex container automatically becomes a flex item anyway, this makes such a display type completely redundant.
Block-level and inline-level boxes, on the other hand, exist in many, many forms. Even flex containers can appear in block-level and inline-level forms, as display: flex and display: inline-flex respectively. Furthermore, display: block and display: inline-block actually have a lot in common, as they are both block containers. The only difference is that one is block-level and the other is inline-level (and the latter always generates a block formatting context, but that's not pertinent here).
So display: block and display: inline-block are actually very similar to display: flex and display: inline-flex respectively in that regard (see Difference between display:inline-flex and display:flex), the difference being that the former pair handles block layout or inline layout (see section 9.2 of the CSS2 spec), and the latter pair handles exclusively flex layout.
If you're asking why flexbox was designed this way, that's something only the CSSWG can answer with certainty, but I can provide an educated guess based on what I've stated above: Since block-level and inline-level boxes can exist in so many different forms for various internal layout types (block, table, flex, grid, etc), defining flex-level counterparts for every single layout type would become extremely unwieldy, even if they did introduce a concept of anonymous flex container boxes which would allow elements to exist as flex items in their own right. This is why css-display-3 redefines the display property to take the form of <display-outside> <display-inside> along with special and legacy values — to accommodate new layout types without having to redefine entire sets of keywords to go along with them.
Your flex items are display: block simply because that's their specified value of display. But they are laid out as flex items, which always obey a set of rules in flex layout, which are loosely based on a combination of various elements of block and inline layout without falling squarely within the domain of either one. This is similar to how a float or an absolutely positioned element cannot be inline even if you specify display: inline or display: inline-block — because floats and absolutely positioned elements always participate in block layout, never inline layout.
Speaking of floats, FYI, floats participate in block formatting contexts, and are therefore part of a certain subset of block layout. They follow a float model, but the float model is integrated with the rest of block layout rather than existing as a completely separate layout type.

CSS Position element on bottom of container without removing it from flow

I have a container with 3 children elements.
<div class="container">
<img />
<div class="element1"></div>
<div class="element2 bottom"></div>
</div>
They must be positioned as shown on the diagram below:
image is in the top of the left column and nothing goes below it (it is the only element in the left column)
element1 is in the top of the right column
element2 is stick to the bottom of the right column (and must not collide with the element1 which is above it)
Does somebody know how to achieve such layout using pure CSS? Ideally I wouldn't like to add any markup, but I can do that if that's the only possible way.
The biggest problem I'm facing here is how to stick that second element (non-image) to the bottom of the container without removing it from the flow. Because if I use position: absolute and remove it from the flow, the elment above it can collide with it (both elements have unknown height).
Here's a pen to work on: http://codepen.io/anon/pen/yNwGvQ
I would suggest you to use two columns in your html and then use the property display: flex; for your right column as suggested in the article A Complete Guide to Flexbox.
http://codepen.io/AlexisBertin/pen/QboYyY
All the HTML:
<div class="container">
<div class="column column-left">
<div class="image">This is an image</div>
</div>
<div class="column column-right">
<div class="element1">This container has dynamic content so it's height is unknown and may change.<br/><br/> Some random content to make it larger. Some random content to make it larger. Some random content to make it larger. Some random content to make it larger. Some random content to make it larger.</div>
<div class="element2">This container also has dynamic content so it's height is unknown and may change</div>
</div>
</div>
Part of this CSS:
.column {
float: left;
height: 100%;
}
.column.column-left { width: 100px; }
.column.column-right {
width: calc(100% - 100px);
display: flex;
flex-direction: column;
justify-content: space-between;
}
Hope you get the idea. Good Luck'.
EDIT:
The easiest way to achieve this without declaring height to the container seems to only create a third parent div to the first block of the second column and define it as flex: 1; while the second block of this same second column would be define as flex: 0;.
http://codepen.io/anon/pen/yNwZmJ
More details explained in the comments.
The easiest solution I figured out is this one:
First you create this CSS:
.container {
width: 400px;
padding: 10px;
border: 1px solid red;
background-color: white;
}
.container > img {
float: left;
}
.container > div {
position: relative;
overflow: auto;
padding-left: 5px;
min-height: 120px;
}
.container > div > .bottom{
position: absolute;
bottom: 0;
display: block;
}
And then use these divs, depending on your content. The first one you use when you know your text is short:
<div class="container">
<img src="http://placehold.it/120x120">
<div>
<div>
<p>This container has dynamic content so it's height is unknown and may change.</p>
</div>
<div class="bottom">
<p>This container also has dynamic content so it's height is unknown and may change</div>
</div>
</div>
The second one you use when you know your text is long
<div class="container">
<img src="http://placehold.it/120x120">
<div>
<div>
<p>This container has dynamic content so it's height is unknown and may change.</p>
<p>Some random content to make it larger. Some random content to make it larger. Some random content to make it larger. Some random content to make it larger. Some random content to make it larger.</p>
</div>
<div>
<p>This container also has dynamic content so it's height is unknown and may change</p>
</div>
</div>
</div>
The difference is that you remove bottom class from the last div in your div that has long text.
Also in your CSS you can see .container > div{... min-height: 120px; ...}, you should set it to height of your image. In case you want the bottom text more down then you have to increase min-height to be bigger than your image height.
Here is it in action: http://codepen.io/anon/pen/YXgBXx

Resources