by

When Properties Compete: How CSS sets dimensions

Reading Time: 4 minutes

The other day I had a fun conversation with a friend and one-time co-worker, Wes Ruvalcaba. We were talking about CSS Grid and the fr unit and how it didn’t quite behave the way (I) expected. Wes and I got to chatting a bit more about our wants and complaints about CSS, and this lead to me wishing for a intrinsic-size property; something where I could tell an element to be sized in a specific proportion. Wes, instead, argued for ew (element width) and eh (element height) properties.

We debated the merits of both (entirely theoretical properties) and Wes understandably raised the issue of “competing properties”:

div {
  intrinsic-size: 3 / 4;
  width: 400px;
  height: 500px;
}

I posited that this shouldn’t be an issue as I believed that I understood how CSS decided dimensions when CSS properties offered conflicting rules for how an element sized itself.

A discussion of theoretical CSS properties led to an scientific study of how elements get their shapes. Turns out my formula of min-* > max-* > dimension > flexWhatever wasn’t entirely true.

My Initial Assumption

My initial assumption was that the browser picked the size of the element following the operation min-* > max-* > dimension

In this scenario, the element will end up with a width of 200px;

div {
  width: 400px;
  max-width: 200px;
}

 

And in this one, the element gets a width of 600px:

div {
  width: 400px;
  max-width: 200px;
  min-width: 600px;
}

Now, we can all agree that the above scenario would be a dumb thing to write in one sitting. But, in large stylesheets where you’re using relative units all over the place and your CSS is written in a pre-processor in across 30 files, it’s a thing that could actually happen. So we need to know that “min always wins”.

The point is, this was my initial assumption, and the codepen shows it to be true:

See the Pen Dimension Competition: Assumption by Paceaux (@paceaux) on CodePen.

And then There’s Flexbox…

“flex-box” is a CSS module that was released five or six years ago that comes with tons of cool features like vertical alignment, even distribution of elements, and “flex-sizing” where an element can size up or down to fill up available space.

Regarding flex-basis

So, in order for my great big bucket of hope for an eventual intrinsic-size property to work, it follows my second hypothesis that min-* > max-* > dimension > flex-basis

That is to say, this will produce an element 400px wide.

div {
 width: 400px;
 flex-basis: 300px;
}

Flex-basis is a flip-flopper

So my assumption was partially wrong.

Turns out that flex-basis sets the dimensions if it’s greater than the dimensions.

This this scenario, the element will be  800px wide:

div {
  flex-basis: 800px;
  width: 400px;
}

But in this one, it will be 400px wide:

div {
  width: 400px;
  flex-basis: 50px;
}

Look at my basic flex-basis example and see:

See the Pen Dimension Competition: Flex Basis Dimensions by Paceaux (@paceaux) on CodePen.

When min-* and max-* come in, everything’s predictable again

So while flex-basis flip-flops if you’ve got a set dimension, it’s powerless against max-* and min-*.

The following proves that the order of precedence is min-* > max-* > bigger flex-basis > dimension > smaller flex-basis :

See the Pen Dimension Competition: Flex Basis + min/max by Paceaux (@paceaux) on CodePen.

Boy is flex-grow strange

The behaviors of flex-grow are so strange, we’ve got to look at this piece-by-piece.

flex-grow beats out the dimensions

If you have something like this, you can predictably expect the element to be larger than 400px. After all, that’s kinda the point of flex-grow:

div {
width: 400px;
flex-grow: 1;
}

flex-grow is powerless against min-* or max-*

Assume that you’ve got a scenario with a flex-grow and a max-width, then the element is sized against the max-width property. So this scenario gets you an element with a width of 50px:

div {
 max-width: 50px;
 flex-grow: 1; 
}

And very predictably, this scenario will get you an element that’s as wide as the container (because min-width is a starting point, not an ending point) :

div {
  min-width: 600px;
  flex-grow: 1;
}

So this kinda makes some sense. I’ve always understood flex-grow to mean, “permission to grow”, and permissions can come with restrictions. This demonstrates the formula min-* > max-* > flex-grow-size > dimensions :

See the Pen Dimension Competition: Flex Grow + min/max by Paceaux (@paceaux) on CodePen.

But what if you put min-* and max-* together with flex-grow?

This get’s you an element that’s 900px:

You: “Grow to your heart’s content. But you can’t get bigger than 900px. Oh, and start at a minimum size of 600px”

Buddy the Browser: “Sweet, I can get up to 900px. Maximum effort!”

div {
  max-width: 900px;
  min-width: 600px;
  flex-grow: 1;
}

But what if max-width is less than min-width
This will get you a width of 600px:

You: “Hey buddy, you can grow as much as you want. But you can’t get bigger than 200px. But your minimum size is 600px”.

Buddy the Browser: “600px boss. Got it”

You: “Wait, what?”

div {
  min-width: 600px;
  max-width: 200px;
  flex-grow: 1;
}

Let me phrase another way:

If you’re using min-width, max-width, and flex-grow together on the same element and your max-width is less than your min-width, then the element will not grow as much as it wants. It will grow to the min-width.

That ain’t right. min-width is supposed to be the starting point for growth. Not the ending point. Check the Pen if you don’t believe me

See the Pen Dimension Competition: Flex Grow + min + max by Paceaux (@paceaux) on CodePen.

TL;DR

As it turns out, the way an element gets its dimensions isn’t as predictable as I’d predicted. My whole theory on an intrinsic-ratio property is out the door because the browser doesn’t have a strict ranking of properties to determine the winner.

flex-basis sizes the element only if it’s bigger than the element’s set size.

flex-grow doesn’t strictly tell the element to grow. It has a mini death-match between min-* and max-* to see who the biggest is, and it stops growing at the largest between the two. So that’s weird.

I’ll need to do some experimenting with adding flex-basis into that little death match to see if it gets any weirder.

Stay safe out there. CSS is weird.

1 Comment


  1. Hey Frankie, when you say…

    “My whole theory on an intrinsic-ratio property is out the door because the browser doesn’t have a strict ranking of properties to determine the winner.”

    …I think you are missing out on how flex-grow, as well as flex-shrink, provide floats as fractional values adding up the the whole flex context. Meaning, that grow doesn’t mean to just allow a flexing items dimension to grow. It means that the flex-grow value is how much fractional space should this item fill against the sum of all flex-grow values in this flex container. It doesn’t just tell a dimension to grow.

    And there is no death match, the min/max ranking still holds in a flex context. Min always wins over max. Just like you show in your third code example above. Min will always “pull up” that dimension when its max counterpart is too small to allow that flex-grow. Because flex-grow couldn’t be larger than your max, so it surely won’t be larger than the min.

    As for your intrinsic-ratio property concept, don’t give up. I’d suggest playing around with img tags as flex items. They do in fact have an intrinsic ratio to their dimensions/layout and their currently spec’d behavior will be the groundwork for any aspect/intrinsic ratio specifications in the future.

    Good luck sir.

Comments are closed.