Why Does Width and Height of a Flex Item Affect How a Flex Item Is Rendered

Why does width and height of a flex item affect how a flex item is rendered?

An initial setting of a flex container is align-items: stretch.

That means that flex items will expand across the cross axis of the container.

In a row-direction container, like in the question, the cross axis is vertical.

That means the items (images, in this case) will cover the full height of the container.

However, when a flex item has a defined cross size, that overrides the stretch default.

From the spec:

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.

stretch

If the cross size property of the flex item computes to auto, and
neither of the cross-axis margins are auto, the flex item is
stretched.

This is the key language:

If the cross size property of the flex item computes to auto

And this is how the spec defines "cross size property":

The width or height of a flex item, whichever is in the cross
dimension, is the item’s cross size. The cross size property is
whichever of width or height that is in the cross dimension.

https://www.w3.org/TR/css-flexbox-1/#cross-size-property


So your code appears to be playing out as defined in the spec.

This is what you have:

.flex-parent {  display: flex;  max-height: 10vh;}
<div class="flex-parent">  <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Red_eyed_tree_frog_edit2.jpg/320px-Red_eyed_tree_frog_edit2.jpg">  <img width="320" height="240" src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Red_eyed_tree_frog_edit2.jpg/320px-Red_eyed_tree_frog_edit2.jpg"></div>

How flex affects the width of an image?

First, you should note that flex-grow is by default equal to 1 so you don't need to set it. Then, you need to understand "cyclic dependency" and "percentage sizing".

In your case, you have a image that should be 100% of its parent width but its parent is a flex item and its width depend on its content (the image width as well).

I have explained a similar behavior here: How do browsers calculate width when child depends on parent, and parent's depends on child's. The same apply in your case and the width:100% will disable the minimum contribution of the image size. It's like the image doesn't exist initially.

Here is the steps the browser will perform.

First, each column is sized with its content. No shrink is occurring (It's like setting flex-shrink:0)

#about {
border: 2px solid blue;
}

.row {
display: flex;
border: 2px solid black;
}

img {
display: block;
width: auto;
}

.column {
flex-shrink: 0;
border: 2px dashed red;
}
<section id="about">
<div class="row">
<div class="column">
<img src="https://i.stack.imgur.com/yat2N.jpg" alt="Sample Image">
</div>
<div class="column"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur quibusdam dicta dolore suscipit quidem, hic nihil aliquid officia porro illum! Necessitatibus cupiditate, sapiente cum recusandae tenetur dolore veritatis in temporibus perferendis.
Ex corrupti voluptatibus eaque aliquam quis soluta veniam non dicta repellendus ea iure temporibus assumenda placeat accusantium quae iste, corporis maxime dolorum quisquam neque est sint asperiores doloribus. Quibusdam ducimus saepe distinctio
illum veniam voluptates amet quod perferendis dolorem, deleniti mollitia. Ab aperiam, ea itaque tempore molestias ullam sint accusamus totam reiciendis laborum. At natus consequatur ex officia. Porro dolor accusamus blanditiis nam commodi provident
assumenda facere adipisci perferendis. </div>
</div>
</section>

Why do flexbox item images resize differently according to their initial size?

tl;dr: The items are resizing differently because they have a different flex-basis, which is based on the image size and is the value that they start flexing (shrinking) from. Every item has to shrink, but the larger items shrink from a larger starting-point, so they're still larger after the shrinking. (There's also a late-in-the-algorithm "clamp" to prevent them from being bigger than their max-width; this is what keeps the large items from being crazy-huge in this case. But importantly, they start their shrinking from their flex-basis, and the max-width clamp is an afterthought.)

FIX: If you give each flex item the same flex-basis, e.g. flex-basis:250px (the same as their max-width), you'll probably get the result you're looking for. Updated fiddle: http://jsfiddle.net/brcrnj80/10/

(As noted in another answer, flex: 1 1 0 (which can be expressed more concisely as flex:1) will also work -- that's setting the flex-basis to 0, and allowing the flex items to grow (instead of forcing them to shrink) from there, up to their max-width. Produces the same results, just via a different route.)

LONGER EXPLANATION: Here's what happens:

  1. By default, everything has flex-basis:auto, which in this case means that each flex item starts out its flexing at its image's intrinsic width. So your huge-image flex items have a huge flex basis, and your small-image flex items have a small flex basis.
  2. We see if the sum of the flex items' flex-basis values are larger than the container. They are (because some of your images are huge). So, we have to shrink things.
  3. We shrink each flex item "fairly", starting from its flex-basis, by whatever portion is necessary to make all of the items fit. So e.g. each item loses 1/4 of its width, for example (if that happens to be the right fraction to make them all exactly fit the container).
  4. NOW, we check if any of these "tentative" item sizes violate the item's max-width. Your flex items with large images will likely violate their max-width at this state, because e.g. even if we take away 1/4 of their size (in my example), they're still much larger than their max-width:250px. For any such violations, we freeze the item at its max-width, and we restart the shrinking process. (We do something similar for min-width violations.)
  5. In the restarted shrinking process, the large images are frozen at 250px-width, and the smaller images are responsible for all of the shrinking.

So if there's not enough space for everyone to have 250px of width, your small flex items end up having to do all of the shrinking. (Unless we're constrained enough that the large items would be shrunk to be less than 250px in the first round of shrinking -- e.g. if we're shrinking each item by 90%, say. Though, then the small items will also be shrunk by 90%, and they'll be less than their min-width:60px, and they'll get frozen at that size and we'll restart the shrinking in the same way that I described above).

See the "Resolving Flexible Lengths" chunk of the spec for more details if you're curious.

Why do flex-item images not scale height?

I believe this is a bug with the most recent production chrome (48). Does running this in console "resolve" your issue (assuming you have jquery running): $('img').css('min-height',0);

If so (or alternatively) you should be able to give your images a min-height of 0 in css. That should do the trick as a workaround until it's fixed.

Found this on: https://github.com/angular/material/issues/6841

Chrome is a great browser, but occasionally the flex-box support breaks with updates =/

Why don't flex items shrink past content size?

The Automatic Minimum Size of Flex Items

You're encountering a flexbox default setting.

A flex item cannot be smaller than the size of its content along the main axis.

The defaults are...

  • min-width: auto
  • min-height: auto

...for flex items in row-direction and column-direction, respectively.

You can override these defaults by setting flex items to:

  • min-width: 0
  • min-height: 0
  • overflow: hidden (or any other value, except visible)

Flexbox Specification

4.5. Automatic Minimum Size of Flex
Items

To provide a more reasonable default minimum size for flex items, this
specification introduces a new auto value as the initial value of
the min-width and min-height properties defined in CSS 2.1.

With regard to the auto value...

On a flex item whose overflow is visible in the main axis, when specified on the flex item’s main-axis min-size property, specifies an automatic minimum size. It otherwise computes to 0.

In other words:

  • The min-width: auto and min-height: auto defaults apply only when overflow is visible.
  • If the overflow value is not visible, the value of the min-size property is 0.
  • Hence, overflow: hidden can be a substitute for min-width: 0 and min-height: 0.

and...

  • The minimum sizing algorithm applies only on the main axis.
  • For example, a flex item in a row-direction container does not get min-height: auto by default.
  • For a more detailed explanation see this post:

    • min-width rendering differently in flex-direction: row and flex-direction: column

You've applied min-width: 0 and the item still doesn't shrink?

Nested Flex Containers

If you're dealing with flex items on multiple levels of the HTML structure, it may be necessary to override the default min-width: auto / min-height: auto on items at higher levels.

Basically, a higher level flex item with min-width: auto can prevent shrinking on items nested below with min-width: 0.

Examples:

  • Flex item is not shrinking smaller than its content
  • Fitting child into parent
  • white-space css property is creating issues with flex

Browser Rendering Notes

  • Chrome vs. Firefox / Edge

    Since at least 2017, it appears that Chrome is either (1) reverting back to the min-width: 0 / min-height: 0 defaults, or (2) automatically applying the 0 defaults in certain situations based on a mystery algorithm. (This could be what they call an intervention.) As a result, many people are seeing their layout (especially desired scrollbars) work as expected in Chrome, but not in Firefox / Edge. This issue is covered in more detail here: flex-shrink discrepancy between Firefox and Chrome

  • IE11

    As noted in the spec, the auto value for the min-width and min-height properties is "new". This means that some browsers may still render a 0 value by default, because they implemented flex layout before the value was updated and because 0 is the initial value for min-width and min-height in CSS 2.1. One such browser is IE11. Other browsers have updated to the newer auto value as defined in the flexbox spec.


Revised Demo

.container {  display: flex;}
.col { min-height: 200px; padding: 30px; word-break: break-word}
.col1 { flex: 1; background: orange; font-size: 80px; min-width: 0; /* NEW */}
.col2 { flex: 3; background: yellow}
.col3 { flex: 4; background: skyblue}
.col4 { flex: 4; background: red}
<div class="container">  <div class="col col1">Lorem ipsum dolor</div>  <div class="col col2">Lorem ipsum dolor</div>  <div class="col col3">Lorem ipsum dolor</div>  <div class="col col4">Lorem ipsum dolor</div></div>

Limits of align-items:stretch on flexbox?

I think you've stumbled upon an interesting problem with flex layout. And you've phrased it exactly right (to paraphrase):

"What are the limits of align-items: stretch?"

Based on your example, there appear to be some limits:

Here's where I am so far, based on experimentation with Chrome:

  1. According to the flexbox spec:

    In a multi-line flex container, the cross size of each line is the minimum size necessary to contain the flex items on the line.

    Therefore, in your layout, which is set to flex-wrap: wrap (making it multi-line), your requirement:

    "I am trying to make a flexbox grid of items, where each row will stretch to be the same height as the tallest item."

    … should be met.

    But it isn't.

    As you pointed out:

    "... no matter what I seem to try either the content of the largest item is pushed outside and overflows, or the other items do not stretch."



  1. I think the reason the items in each row don't stretch to the height of the tallest item in that row has to do with another flex function: align-content: stretch.

    This function forces the flex container to create flex lines of equal height, limiting the growth of flex items in each row.

    But changing the value of align-content, and setting heights and min-heights on the container, don't alter the behavior. Seems like a pretty clear align-items limitation to me.



  1. You may not be able to achieve flex items that stretch to the height of the tallest item in the row in a multi-line container (it's certainly possible in a single line container), but you can get them all to be equal height, which I understand is something that may not work for you.

    Here are a few ideas that may move you closer to an overall solution:

    • Method #1: Remove width: 100px from the image elements (demo). Obviously, this isn't a useful solution, as it allows the images to expand to their natural width. It's just meant to identify a problem area. Also, it illustrates items overlapping rows (flex lines), which supports the theory of align-content serving as a constraint on align-items.

    • Method #2: Enable flex-shrink on the image containers. So instead of .lower { flex: 0 0 50% } use flex: 0 1 50%. This allows the container to fit within the row / flex line (demo).

    • Method #3: Add overflow: auto to the flex items (demo). Again, probably not useful, but establishes equal height rows.


Lastly, maybe I missed something entirely here, which would be good news, since it would be nice to know that flex items can stretch to the height of the tallest item in each row, as defined in the spec. Good luck.

More info:

  • Equal height rows in a flex container
  • How does flex-wrap work with align-self, align-items and align-content?

What are the differences between flex-basis and width?

Consider flex-direction

The first thing that comes to mind when reading your question is that flex-basis doesn't always apply to width.

When flex-direction is row, flex-basis controls width.

But when flex-direction is column, flex-basis controls height.


Key Differences

Here are some important differences between flex-basis and width / height:

  • flex-basis applies only to flex items. Flex containers (that aren't also flex items) will ignore flex-basis but can use width and height.

  • flex-basis works only on the main axis. For example, if you're in flex-direction: column, the width property would be needed for sizing flex items horizontally.

  • flex-basis has no effect on absolutely-positioned flex items. width and height properties would be necessary. Absolutely-positioned flex items do not participate in flex layout.

  • By using the flex property, three properties – flex-grow, flex-shrink and flex-basis – can be neatly combined into one declaration. Using width, the same rule would require multiple lines of code.


Browser Behavior

In terms of how they are rendered, there should be no difference between flex-basis and width, unless flex-basis is auto or content.

From the spec:

7.2.3. The flex-basis property

For all values other than auto and content, flex-basis is resolved the same way as width in horizontal writing modes.

But the impact of auto or content may be minimal or nothing at all. More from the spec:

auto

When specified on a flex item, the auto keyword retrieves the value
of the main size property as the used flex-basis. If that value is
itself auto, then the used value is content.

content

Indicates automatic sizing, based on the flex item’s content.

Note: This value was not present in the initial release of Flexible
Box Layout, and thus some older implementations will not support it.
The equivalent effect can be achieved by using auto together with a
main size (width or height) of auto.

So, according to the spec, flex-basis and width resolve identically, unless flex-basis is auto or content. In such cases, flex-basis may use content width (which, presumably, the width property would use, as well).


The flex-shrink factor

It's important to remember the initial settings of a flex container. Some of these settings include:

  • flex-direction: row - flex items will align horizontally
  • justify-content: flex-start - flex items will stack at the start of the line on the main axis
  • align-items: stretch - flex items will expand to cover the cross-size of the container
  • flex-wrap: nowrap - flex items are forced to stay in a single line
  • flex-shrink: 1 - a flex item is allowed to shrink

Note the last setting.

Because flex items are allowed to shrink by default (which prevents them from overflowing the container), the specified flex-basis / width / height may be overridden.

For example, flex-basis: 100px or width: 100px, coupled with flex-shrink: 1, will not necessarily be 100px.

To render the specified width – and keep it fixed – you will need to disable shrinking:

div {
width: 100px;
flex-shrink: 0;
}

OR

div {
flex-basis: 100px;
flex-shrink: 0;
}

OR, as recommended by the spec:

flex: 0 0 100px;    /* don't grow, don't shrink, stay fixed at 100px */

7.2. Components of
Flexibility

Authors are encouraged to control flexibility using the flex shorthand
rather than with its longhand properties directly, as the shorthand
correctly resets any unspecified components to accommodate common
uses.


Browser Bugs

Some browsers have trouble sizing flex items in nested flex containers.

flex-basis ignored in a nested flex container. width works.

When using flex-basis, the container ignores the sizing of its children, and the children overflow the container. But with the width property, the container respects the sizing of its children and expands accordingly.

References:

  • Chrome does not expand flex parent according to children's content
  • Flex item overflowing when using flex-basis
  • Difference between width and flex-basis
  • Flex-basis is being ignored when sizing nested flex containers.
  • flex-basis:100px does something different from width:100px+flex-basis:auto

Examples:

  • https://jsfiddle.net/t419zhra/ (source: @Dremora)
  • https://codepen.io/anon/pen/NVxaoy (source @Daniel)
  • https://jsfiddle.net/voc9grx6/ (source: Chromium Bugs)
  • https://jsfiddle.net/qjpat9zk/ (source: Chromium Bugs)

flex items using flex-basis and white-space: nowrap overflow inline-flex container. width works.

It seems that a flex container set to inline-flex doesn't recognize flex-basis on a child when rendering a sibling with white-space: nowrap (although it could just be an item with undefined width). The container doesn't expand to accommodate the items.

But when the width property is used instead of flex-basis, the container respects the sizing of its children and expands accordingly. This is not a problem in IE11 and Edge.

References:

  • inline flex container width not growing
  • Inline flex container (display: inline-flex) is expanding the full width of parent container

Example:

  • https://jsfiddle.net/p18h0jxt/1/ (from first post above)

flex-basis (and flex-grow) not working on table element

References:

  • Why does flex-box work with a div, but not a table?
  • Why doesn't flex-grow: 1 work for a table in Safari? (and Edge)

flex-basis fails in Chrome and Firefox when the grandparent container is a shrink-to-fit element. The set-up works fine in Edge.

  • Absolutely positioned container not expanding width to fit flexbox content

Like in the example presented in the link above, involving position: absolute, the use of float and inline-block, will also render the same flawed output (jsfiddle demo).


Bugs affecting IE 10 and 11:

  • flex shorthand declarations with unitless flex-basis values are ignored
  • flex-basis doesn't account for box-sizing: border-box
  • flex-basis doesn't support calc()
  • Importance is ignored on flex-basis when using flex shorthand

Why does this flex item have its height limited?

OK, I think I figured out what's happening.

Up until flexbox, there was no way to cause an element to be smaller than the size of its content without explicitly settings its width/height. With flexbox, there is a second way.

Para 9.11 of the flexbox spec talks about how to calculate a flex-item's cross size. Basically if the flex item has align-self: stretch set (which in my example it does), it will try to fill the container's flex line (in my example the vertical line, ie. height). This is implicitly setting the flex item's height, without explicitly using the height property. The container itself needs to have a definite height, too, to be handed down to the flex item (in my example the container's height is explicitly set through the height CSS attribute, which I guess ultimately needs to be done at some point in the chain).

However, two conditions need to be met: the browser needs to want to resize the item, and the item needs to be resizable.

To make the browser want to resize the item, it needs to be a flex item that meets the conditions mentioned above for its cross size (which ultimately may determine its width or height) to be calculated by the browser with relation to its container's cross size.

To make the item resizable in this context, it needs to both not have flex-shrink set to 0 (note that flex:none is shorthand for setting flex-shrink to 0, amongst other things), and have overflow:hidden set (presumably giving the browser "permission" to resize the flex item such that its content will be larger than it, and get hidden). Only then will the browser actually resize the flex item itself.

This is notably different behaviour to what happens outside the flexbox model, where the child of a fixed height/width container may have it content clipped, but it will retain the necessary size to hold its content. Part of the content will merely be hidden. Here, the flex item really is being (potentially) resized to be smaller than its content, which has quite important implications (particularly if you want a child element to have a scrollbar for its content).

Better way to set distance between flexbox items

  • Flexbox doesn't have collapsing margins.
  • Flexbox doesn't have anything akin to border-spacing for tables (edit: CSS property gap fulfills this role in newer browsers, Can I use)

Therefore achieving what you are asking for is a bit more difficult.

In my experience, the "cleanest" way that doesn't use :first-child/:last-child and works without any modification on flex-wrap:wrap is to set padding:5px on the container and margin:5px on the children. That will produce a 10px gap between each child and between each child and their parent.

Demo

.upper {
margin: 30px;
display: flex;
flex-direction: row;
width: 300px;
height: 80px;
border: 1px red solid;

padding: 5px; /* this */
}

.upper > div {
flex: 1 1 auto;
border: 1px red solid;
text-align: center;

margin: 5px; /* and that, will result in a 10px gap */
}

.upper.mc /* multicol test */ {
flex-direction: column;
flex-wrap: wrap;
width: 200px;
height: 200px;
}
<div class="upper">
<div>aaa<br/>aaa</div>
<div>aaa</div>
<div>aaa<br/>aaa</div>
<div>aaa<br/>aaa<br/>aaa</div>
<div>aaa</div>
<div>aaa</div>
</div>

<div class="upper mc">
<div>aaa<br/>aaa</div>
<div>aaa</div>
<div>aaa<br/>aaa</div>
<div>aaa<br/>aaa<br/>aaa</div>
<div>aaa</div>
<div>aaa</div>
</div>


Related Topics



Leave a reply



Submit