Barebones CSS for Fluid Images
A few days back CarolSaysThings posed what I expected to be a simple question until I started to question what I already knew. Carol had some image markup (generated by Eleventy Image) and was asking the best way to make the image fluid (match the width of its container). I knew I understood this well enough to use it in my own work but I started to realize that perhaps I didn’t know this topic well enough to teach it so I dove in a little deeper.
Primary goal: I want to test width: 100%
versus max-width: 100%
and how those interact with [width][height]
and srcset
attributes.
Now, my usual take was to pop some width: 100%
CSS on that thing and call it a day. width: 100%
CSS forces the image to fill up the width of the container. This overrides any [width]
attribute you have set. This has the downside that the image can outgrow it’s own dimensions and can appear blurry.
Each case below uses a 200×200 image in both a 150px
container (to shrink) and a 300px
container (to grow).
width: 100%
Without [width][height]
Using [width][height]
The 100%
in max-width
refers to the container dimensions. Sometimes the [width]
attribute will be smaller than this container, which means the image will not always be full width. Practically speaking, the image will never grow larger than its own internal or intrinsic dimensions.
Another way to think about this, the image width can range between 0
and [width]
, depending on the container size.
Editors note: the above section had a pretty glaring error and was corrected thanks to @CarolSaysThings, @HarryMoore2409, and @nhoizey! Sorry about that, y’all.
max-width: 100%
Without [width][height]
Using [width][height]
Let’s use srcset
to add another eligible image width (now 200px and 400px) and see what happens. Spoiler alert: no surprises here! 🎉
srcset and width: 100%
Without [width][height]
Using [width][height]
Keeping our two eligible image widths in play (200px and 400px) let’s swap to use max-width: 100%
.
srcset and max-width: 100%
Without [width][height]
Using [width][height]
This is where the rubber finally meets the road. For me, the above test was the most surprising part of the research—and why a deeper analysis of this perhaps introductory topic was worthwhile (for me).
When I traditionally used width: 100%
it bulldozed the [width]
attribute. But a strict max-width: 100%
now competes with the [width]
attribute. One solution is to add width: auto
to pair with our max-width: 100%
CSS.
September 9, 2021 Update: This article has been amended to add one additional caveat about mixing height: auto
and width: auto
.
That looks like this:
srcset and max-width: 100%
Using [width][height] and width: auto ⚠️ incurs CLS costs
When Eleventy Image generates markup for more than one size, the <img>
element uses the lowest size/quality source. But this behavior has me thinking that when srcset
is in play it should use the largest dimensions for the [width]
and [height]
attributes. I wonder what y’all think about that? Practically, it would look like this:
srcset and max-width: 100%
Using [width="400"][height="400"]
This makes max-width: 100%
a bit more predictable, as the rendered size now matches the behavior when [width][height]
are not included or when width: auto
was left off. The maximum width now correctly matches the intrinsic width of the largest image in our srcset
list.
February 24, 2021 Update: Eleventy Image v0.8.0 was released with the above [width][height]
attribute optimization.
Again—practically I would recommend to pair max-width: 100%
with width: auto
to fix this in the easiest way but this might help avoid some confusion for some folks that aren’t aware of this.
September 9, 2021 Update: This article has been amended to add one caveat about mixing height: auto
and width: auto
.
Conclusions (aka TL;DR)
- All of these approaches operate the same when the container is smaller than the image.
- Using
width: 100%
can give you some blurry images if you’re not careful with your container sizes (without Content Layout Shift penalties). - Using
max-width: 100%
constrains the image to the container, but be careful when you use this withsrcset
—it may cap smaller than you want when using[width]
! Use the largest[width]
and[height]
attributes to fix this. - ⚠️ Do not use
width: auto
paired withheight: auto
as this can incur Content Layout Shift penalties. - But perhaps esoterically I’m walking away with this remaining question: when you have multiple image sizes listed in a
srcset
list, which dimensions do you use for the[width]
and[height]
attributes? I kinda want to use the largest one—any obvious downsides to that?
February 24, 2021 Update: Eleventy Image v0.8.0 was released to use the largest size for the [width][height]
attributes.
Copy and Paste
Writing this blog post has swayed me to part from my width: 100%
ways! I think this is what my boilerplate will be moving forward:
img {
max-width: 100%;
}
img[width][height] {
height: auto; /* Preserve aspect ratio */
}
/* Let SVG scale without boundaries */
img[src$=".svg"] {
width: 100%;
height: auto;
max-width: none;
}
February 24, 2021 Update: Chris Coyier posted a great follow up to this post on CSS Tricks with some super valuable extra information about how srcset
affects rendered image dimensions.
Content Layout Shift Addendum
September 9, 2021 Update: Prior versions of this blog post recommended img[width] { width: auto; }
to solve the problem of images rendering too small when the width
and height
attributes were smaller than the maximum intrinsic size supplied via the srcset
attribute. The best fix here is to use the largest dimensions for your width
and height
attributes (even if you use a lower quality version for the src
).
Additionally, radumicu kindly pointed out that using width: auto
in this way conflicts with the default styles for aspect-ratio
and incurs Content Layout Shift penalties.
I tested and confirmed this to be true when you pair width: auto
and height: auto
together. You can view the tests on Codepen:
- ✅
<img>
with{ max-width: 100% }
no CLS penalties - ✅
<img>
with{ max-width: 100%; width: auto }
no CLS penalties - ✅
<img>
with{ max-width: 100%; height: auto }
no CLS penalties - ❌
<img>
with{ max-width: 100%; width: auto; height: auto }
console logs CLS penalties. - ✅
<img>
with{ width: 100%; height: auto }
no CLS penalties
40 Comments
@MichaelWDelaney
I am so lazy. I figure whatever CSS framework I'm using has solved this for me. I'm turning in my developer card.
@zachleat
I was basically this same thing until about 2 days ago 😅 But I wrote it up so you can just read it and don’t have to write a bunch of tests to learn how it works! 🏆
@niksy
It seems like I’ve been doing similar thig for quite some time, it’s just that it was more like trial and error thing! 😅 github.com/niksy/rational… Thanks for the writeup!
@apleasantview
Mint post! I'm convinced ... I think 😅
@zachleat
Mine is a tiny bit different but yes! zachleat.com/web/fluid-imag…
@CarolSaysThings
This is such a great explanation, thanks for writing it! 🙌🏼 I started my site rebuild layer by layer so I could learn things, and it’s definitely working 😅 I think 2 things surprised me from the off: that I needed CSS at all to get the picture element to be responsive; (+)
@CarolSaysThings
and that when you say max-width: 100% it relates to the image’s width but when you say width: 100% it relates to the image’s container’s width 🤔 The more you know 💫
@zachleat
yeah I agree that’s weird 😅
@rob_dodson
Oh good write up! I think we inadvertently avoided the last issue because the value we use for the width attribute is always the width of the original image.
@zachleat
oh interesting—so a bit of evidence that it’s okay to use larger width/height attributes?
@rob_dodson
Because we know our max column size when someone uploads an image to our cdn we give them back a snippet to use which has the width and height attributes prefilled and it has constrained them to our max column size.
@piccalilli_
This is bloody excellent. Permission to update the modern CSS reset with your findings?
@rob_dodson
Yeah we combine it with srcset and on smaller screens it seems to download the correct smaller image
@rob_dodson
Btw Jen Simmons has a really good video on this that I found super helpful. youtu.be/4-d_SoCHeWE
@zachleat
Of course! Your CSS reset is incredible, btw—well maintained!
@rob_dodson
Maybe also worth mentioning that we inline our css so the column width and img max-width styles are in place very early.
@zachleat
Awesome, thanks!
@niksy
Yes, I was refering to that, basically max-width and height combo! Your solution seems like more explicit and more resilient implementation 😄
@piccalilli_
[blushes] It’s actually 3 squirrels stood on top of each other, in a long coat.
@zachleat
@ericwbailey told me “the marvel is not that the bear dances well, but that the bear dances at all”
@ericwbailey
my_career.txt
@piccalilli_
same tbh
@senthil_hi
Great post. In your demo (SRCSET AND MAX-WIDTH: 100% & Using [width="400"][height="400"]) for the small container, I still see the 400px image rendered in the browser. We should see the 200px image, right? Link zachleat.com/web/fluid-imag…
@iamdtms
Aspect-ratio extension would be nice to have! ;)
@zachleat
This is what I expect to see:
@senthil_hi
Correct. But a 400px image downloaded for a 150px container seems to be expensive, right?
@zachleat
My hunch is that using these test cases to analyze which image is selected in srcset might not be the best thing—I’d need to learn more about how the browser coalesces multiple requests of differing sizes. Note that the URLs for the images are the same on the left/right
@HarryMoore2409
You say “The 100% in max-width refers to the image’s dimensions”, is that true? I thought % in CSS was based on the parent element size
@nhoizey
Currently reading your article, I'm really surprised by "The 100% in max-width refers to the image’s dimensions". IMHO, these 100% refer to the container width.
@nhoizey
You’re welcome! 👍
@thedamon
I never thought of tagging styles to whether the height/width attribute is set! Very cool. Do you have thoughts on lighthouse yelling about missing width attributes though? Most of our images are sized with css and also have sizes attribute set so... do I ignore that?
@zachleat
Yeah definitely add them, they’re an important performance optimization to establish the image’s aspect ratio to prevent layout shift!
@thedamon
So if the image is fluid and 50% width.. do I put the intrinsic width (which I do not know) or what? Typically width is for display so this guideline deeply messes with me (and also changes image[width] bit in your snippet)
@cramforce
I wonder if there is ever a good reason to use width: auto? Could this be a warning in lighthouse?
@zachleat
I mean, it does solve the [width][height] versus [srcset] conflict issue. See <img width="200"> in this 300px container with a srcset that has a 400w source in it (it should fit to the container, imo). But mostly I encourage folks to width/height to match your larg… Truncated
@cramforce
So people are using srcset with different aspect-ratio images? Agreed on largest intrinsic size being the right choice!
@zachleat
(all that to say, I’d be a fan of a warning)
@zachleat
oh, I wouldn’t think so—no. I only meant that solving that problem with `width: auto` instead of changing your attributes has non-obvious CLS consequences (imo)
@cramforce
Makes sense.
@radumicu
🥳 Thank you for that article, I did the same tests over and over again and never occurred to me to save the learning to get back to them. Now the theme song from Captain Planet comes to mind - "With out powers combined" the www ... is not that hard 😜