Zach’s ugly mug (his face)

Zach Leatherman

Barebones CSS for Fluid Images

January 30, 2021 #13 Popular Inspired by a tweet.

A few days back CarolSaysThings’s AvatarCarolSaysThings 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]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

Using [width][height]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

Using [width][height]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

Using [width][height]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

Using [width][height]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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"]

The sample Nebula Image from Unsplash on the Eleventy Image docs
The sample Nebula Image from Unsplash on the Eleventy Image docs

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 with srcset—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 with height: 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’s Avatarradumicu 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:

Zach’s ugly mug (his face)

Zach is a builder for the web with IndieWeb Avatar for https://www.netlify.comNetlify. He created the IndieWeb Avatar for https://www.11ty.devEleventy site generator and is still fixated on web fonts. His public speaking résumé includes talks in eight different countries at events like Jamstack Conf, Beyond Tellerrand, Smashing Conference, CSSConf, and The White House. He is an emeritus of Filament Group, NEJS CONF, and still helps out with nebraskajs’s AvatarNebraskaJS. Read more about Zach »

Previous
Jamstack 101: Getting Started with Eleventy
Next
Don’t Shut Down Your Business! Instead Use Eleventy Image

69 Retweets

IndieWeb Avatar for https://www.alvinashcraft.comMWDelaneyMattia AstorinoSami KeijonenCarol 🌻Rob DodsonJens GrochtdreisMatthias OttAndy BellРинат Валиуллов (хи/хим)Stephanie EcklesSaravanaMattDan DenneyPablo Lara HMohammad ArifLee PearsonFabrice Gangler 🔗Becky LingafelterMichael StramelВеб-стандартыFynn Beckerdailydevlinks.IndieWeb Avatar for https://coduza.comChris HeilmannNicolas HoizeyPatidouIndieWeb Avatar for https://reddits.contractwebsites.comIndieWeb Avatar for https://reddits.contractwebsites.comIndieWeb Avatar for https://www.lireo.comIndieWeb Avatar for https://tympanus.netPablo Lara HDavid BissetTrevor PiercePinboard PopularSpeckyboyAndyFree FrontendJoulseFront-End FrontIndieWeb Avatar for https://711web.comCSS BasicsIndieWeb Avatar for https://techylegends.comIndieWeb Avatar for https://codedigger.caIndieWeb Avatar for https://711web.comIndieWeb Avatar for https://711web.comIndieWeb Avatar for https://www.webwaycanada.caIndieWeb Avatar for https://softwaremile.comIndieWeb Avatar for https://www.scoop.itIndieWeb Avatar for http://34.86.126.177IndieWeb Avatar for https://wpnewshub.comIndieWeb Avatar for https://docuneedsph.comIndieWeb Avatar for https://techfans.co.ukIndieWeb Avatar for https://www.webmastersgallery.comIndieWeb Avatar for https://richard.blogIndieWeb Avatar for https://assuagetech.netIndieWeb Avatar for https://docuneedsph.comIndieWeb Avatar for https://speckyboy.comIndieWeb Avatar for http://uiux.design.blogIndieWeb Avatar for https://docuneedsph.comJulian@simounet@mastodon.simounet.net\\ uto-usui //CSS Tips BotEngage WebAlessio CaroneRaduadamAnghelos Coulon

128 Likes

RaduDustin HlavaJon KupermanJordan WinickJacky AlcinéXavier ZaławaMarkus Oberlehner 🔭royciferHarry MooreKyle HallDemon NewmanCarol 🌻Peter AntoniusNicolas HoizeyÁngel GuerraNicolas HoizeyGaël PoupardRob DodsonAdrian CastellanosAbdessalammason conkrightParkerBond usePrivilege(stopRacism)U+2B0Bx3Keith PurtellEric WallaceDave RupertEthan MarcotteJeremy WagnerFynn BeckerMikaelaAgility CMSMaëligGaël PoupardSteven ParkJeremy Van StaalduineDanny de VriesJohn JordanMatt SecoskeStefan RüschenbergSamrat GuptaNatalie ChinUsama Iqbal (گوگل)Gautam KumarBruno Carvalhoscottunderwood.meVladislav Shkodinrizwana akmal khanpozole con repolloKevin McLoughlinNeoDarqueEric CheungrazhMarisa DeMeglioMichael StramelIago BarreiroMarcamillianCarles MuiñosBrett JankordGraham BancroftMarco useCauseAndEffect()Ilya StreltsynBecky LingafelterOsmelPeter AntoniusandeySlightly WarpedParkerBond usePrivilege(stopRacism)Miguel Ángel DuránNikita Voloboevjordi colomereusebioJessmonchowizThis Dot LabsLee PearsonJody DonettiElle Townsend🌸Arthur MeloAna RodriguesEric BaileydevBrett EvansAntonio Laguna ツAurora Johansen-WardigoTodd 🦞AllisonMatthias WestonStephen Scott 💙💚Jewwy QadriRattleknife DesignGreg HowleyFlorian GeierstangerShane DuffIvan NikolićLucas HardisonJake DohmDavid EastJacob LeechRachel LeggettStephanie EcklesMichelle BarkerRick BergfalkJo DoddAndy BellCorey MegownJohn F Croston IIIYomar Miranda ⚡︎Matthew Dean™ShawI am Wes Wilson 👋Adam Di MarioJens GrochtdreisEthan MarcotteMark McLaughlinMatthias OttMWDelaneyDave 🧱Marc Littlemore 🤖Jesse HeadySteven PriceCarol 🌻Bryson GilbertGlennDyour favorite JackiePhilippRob DodsonEric WallaceTanner DolbyPatrick Connors
40 Comments
  1. MWDelaney

    @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.

  2. Zach Leatherman

    @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! 🏆

  3. Ivan Nikolić

    @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!

  4. Cristovao Verstraeten

    @apleasantview

    Mint post! I'm convinced ... I think 😅

  5. Zach Leatherman

    @zachleat

    Mine is a tiny bit different but yes! zachleat.com/web/fluid-imag…

  6. Carol 🌻

    @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; (+)

  7. Carol 🌻

    @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 💫

  8. Zach Leatherman

    @zachleat

    yeah I agree that’s weird 😅

  9. Rob Dodson

    @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.

  10. Zach Leatherman

    @zachleat

    oh interesting—so a bit of evidence that it’s okay to use larger width/height attributes?

  11. Rob Dodson

    @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.

  12. Andy Bell

    @piccalilli_

    This is bloody excellent. Permission to update the modern CSS reset with your findings?

  13. Rob Dodson

    @rob_dodson

    Yeah we combine it with srcset and on smaller screens it seems to download the correct smaller image

  14. Rob Dodson

    @rob_dodson

    Btw Jen Simmons has a really good video on this that I found super helpful. youtu.be/4-d_SoCHeWE

  15. Zach Leatherman

    @zachleat

    Of course! Your CSS reset is incredible, btw—well maintained!

  16. Rob Dodson

    @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.

  17. Zach Leatherman

    @zachleat

    Awesome, thanks!

  18. Ivan Nikolić

    @niksy

    Yes, I was refering to that, basically max-width and height combo! Your solution seems like more explicit and more resilient implementation 😄

  19. Andy Bell

    @piccalilli_

    [blushes] It’s actually 3 squirrels stood on top of each other, in a long coat.

  20. Zach Leatherman

    @zachleat

    @ericwbailey told me “the marvel is not that the bear dances well, but that the bear dances at all”

  21. Eric Bailey

    @ericwbailey

    my_career.txt

  22. Andy Bell

    @piccalilli_

    same tbh

  23. Senthil P

    @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…

  24. Dohány Tamás

    @iamdtms

    Aspect-ratio extension would be nice to have! ;)

  25. Zach Leatherman

    @zachleat

    This is what I expect to see:

  26. Senthil P

    @senthil_hi

    Correct. But a 400px image downloaded for a 150px container seems to be expensive, right?

  27. Zach Leatherman

    @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

  28. Harry Moore

    @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

  29. Nicolas Hoizey

    @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.

  30. Nicolas Hoizey

    @nhoizey

    You’re welcome! 👍

  31. Demon Newman

    @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?

  32. Zach Leatherman

    @zachleat

    Yeah definitely add them, they’re an important performance optimization to establish the image’s aspect ratio to prevent layout shift!

  33. Demon Newman

    @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)

  34. Malte Ubl

    @cramforce

    I wonder if there is ever a good reason to use width: auto? Could this be a warning in lighthouse?

  35. Zach Leatherman

    @zachleat

    I mean, it does solve the [width][height] versus [srcset] conflict issue. See 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 largest in srcset

  36. Malte Ubl

    @cramforce

    So people are using srcset with different aspect-ratio images? Agreed on largest intrinsic size being the right choice!

  37. Zach Leatherman

    @zachleat

    (all that to say, I’d be a fan of a warning)

  38. Zach Leatherman

    @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)

  39. Malte Ubl

    @cramforce

    Makes sense.

  40. Radu

    @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 😜

Shamelessly plug your related post:

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)