Tagged “rwd”
An interactive guide to CSS Container Queries, by Ahmad Shadeed
This is a wonderful guide that’s choc-full of practical examples.
CSS container queries help us to write a truly fluid components that change based on their container size. In the next few years, we’ll see less media queries and more container queries.
Lots of inspiration here for when I start using Container Queries in earnest.
First play with CSS Container Queries
In responsive design we generally want a single component to get different styles in different contexts. Up until recently the prevailing method of context-awareness was to use a CSS media query to query the viewport size. This wasn’t ideal. For example you might want an component to be styled differently when in a narrow context such as a sidebar (regardless of the device size), and viewport-based queries don’t help with that.
But everything has changed. We can now use CSS to query the size of any given container and this feature is supported in all major browsers.
There’s a bit of new syntax to learn, so I recently had my first play with container queries on codepen.
My pen is pretty trivial, but the goal was specifically to do the most minimal test that lets me test-drive the key syntax. It turns out that it’s quite straightforward.
Define an element as a container:
.sidebar {
container: ctr-sidebar / inline-size;
}
Change the styles of another element (.foo
) when it’s inside that container and the container’s inline-size (the logical property name for width
) matches a given query:
@container ctr-sidebar (max-width: 300px) {
.foo {
// context-specific styles go here
}
}
Note that you could also omit the ctr-sidebar
context in the above query, if you wanted the change to apply in all defined containers.
My new syntax for modern, responsive blog images
I’ve started trialling different HTML and technologies for the “simple” responsive images (i.e. not art-directed per breakpoint) used in blog articles on this site. I’m continuing to lean on Cloudinary as my free image host, CDN and format-conversion service. But at the HTML level I’ve moved from a complicated <img srcset>
based approach that includes many resized versions of the same image. I now use a simpler <picture>
and <source>
based pattern that keeps the number of images and breakpoints low and instead – by using the source element’s type
attribute – takes advantage of the performance gains offered by the new avif
and webp
image formats.
My new approach is based on advice in Jake Archibald’s brilliant article Halve the size of images by optimising for high density displays. Jake explains that the majority of your traffic likely consists of users with high density screens so when we can combine optimising for that and making performance gains in a progressively enhanced way, we should!
Jake offers a “lazy but generally good enough” approach:
Here's the technique I use for most images on this blog: I take the maximum size the image can be displayed in CSS pixels, and I multiply that by two, and I encode it at a lower quality, as it'll always be displayed at a 2x density or greater. Yep. That's it. For 'large' images in blog posts like this, they're at their biggest when the viewport is 799px wide, where they take up the full viewport width. So I encode the image 1,598 pixels wide.
<picture>
<source type="image/avif" srcset="red-panda.avif" />
<source type="image/webp" srcset="red-panda.webp" />
<img src="red-panda.jpg" width="1598" height="1026" alt="A red panda" />
</picture>
So, if you want your images to be as sharp as possible, you need to target images at the user's device pixels, rather than their CSS pixels. To encode a 2x image, I throw it into Squoosh.app, and zoom it out until it's the size it'll be displayed on a page. Then I just drag the quality slider as low as it'll go before it starts looking bad.
Taking Jake’s guidance and tweaking it for my Cloudinary-based context, my recent post April 2022 mixtape included its image like so:
<figure>
<picture>
<source type="image/avif" srcset="https://res.cloudinary.com/…/f_avif,q_auto,w_1292/v1654433393/mato_1500_squooshed_mozjpg_xjrkhl.jpg" />
<source type="image/webp" srcset="https://res.cloudinary.com/…/f_webp,q_auto,w_1292/v1654433393/mato_1500_squooshed_mozjpg_xjrkhl.jpg" />
<img class="u-full-parent-width" src="https://res.cloudinary.com/…/f_jpg,q_auto,w_1292/v1654433393/mato_1500_squooshed_mozjpg_xjrkhl.jpg" width="1292" height="1292" alt="Side A of the 7-inch vinyl release of Mato’s “Summer Madness" loading="lazy" decoding="async" />
</picture>
<figcaption>Mato’s “Summer Madness”, as featured on the mix</figcaption>
</figure>
And my process was as follows:
- Take a photo (for me, that’ll be on my phone). Without doing anything special it’ll already be wide enough.
- Apple use the HEIC format. To get around that, do: Share, Copy, open Files > HEIC to JPG, paste and it’ll save as JPG.
- Drop it into Squoosh and do the following:
- rotate it if necessary
- resize it to 1300 wide (in my current layout
1292
is twice as wide (2 × 646) as the image would need to go, and I just round up to 1300) - reduce its quality a bit (stopping before it gets noticeably bad)
- Encode that as a
mozjpg
which gave the best size reduction and as far as I can tell, is a safe approach to use. - Upload to my Cloudinary account then copy its new Cloudinary URL.
- Prepare the image HTML per the above snippet. The first
source
tells Cloudinary to use formatavif
, while for the second source it’swebp
, and for the fallbackimg
it’sjpg
. - Check the rendered image in a browser to confirm that the modern formats are being used.
I’ll DRY-up that HTML into an 11ty shortcode in due course.
I’ve no doubt that I’ll be getting some of this wrong – this stuff gets pretty complicated! For example I note my image file size is still quite large so I wonder if I should be manually creating the avif
and webp
versions in Squoosh myself to ensure getting the savings that make this approach worthwhile, rather than handing the conversion off to Cloudinary. (However this would mean having to host more images…)
In the meantime however, I’m happy that this approach has simplified the mental overhead of handling modern, responsive blog images, and optimising it can be a work in progress.
Additional references
Min-Max clamp calculator, by 9elements
Here’s a handy tool from the smart folks at 9elements for making a value – such as a font-size, or margin – fluidly responsive. In their words the tool…
calculates the CSS clamp formula to interpolate between two values in a given viewport range.
It’s inspired by Utopia but is for situations when you only need a single clamp formula rather than one for each interval in a type or spacing scale.
Beyond that, the website for this tool just looks great and is choc-full of tricks. It uses some snazzy CSS, includes Zach Leatherman’s details-utils web component for animated disclosures and is made with Astro, Svelte and PostCSS.
Partnering with Google on web.dev (on adactio.com)
At work in our Design System team, we’ve been doing a lot of content and documentation writing for a new reference website. So it was really timely to read Jeremy Keith of Clearleft’s new post on the process of writing Learn Responsive Design for Google’s web.dev resource. The course is great, very digestible and I highly recommend it to all. But I also love this new post’s insight into how Google provided assistance, provided a Content handbook as “house style” for writing on web.dev and managed the process from docs and spreadsheets to Github. I’m sure there will be things my team can learn from that Content Handbook as we go forward with our technical writing.
Theming to optimise for user colour scheme preference
“Dark mode” has been a buzz-phrase in web development since around 2019. It refers to the ability provided by modern operating systems to set the user interface’s appearance to either light or dark. Web browsers and technologies support this by allowing developers to detect whether or not the OS provides such settings, and if so which mode the user prefers. Developers can create alternate light and dark themes for their websites and switch between these intelligently (responsively?) to fit with the user’s system preference.
I’ve been meaning to do some work on this front for a while and finally got around to it. (You might even be reading this post with your computer’s dark colour scheme enabled and seeing the fruits of my labour.) Here’s how I set things up and the lessons I learned along the way.
Switching your computer’s appearance
On a Mac:
System Preferences > General > Appearance
On Windows:
Colors > Choose your color
On iOS:
Settings > Display & Brightness > Appearance
On Android:
Settings > Dark Theme
Emulating this in browser DevTools
In Chromium-based browsers you can open DevTools, then open the Rendering
tool (which might be hidden behind More Tools). In the Emulate CSS media feature prefers-color-scheme dropdown, select prefers-color-scheme: dark or prefers-color-scheme: light.
The theme-color HTML meta tag
You can use the theme-color
HTML meta tag to indicate to the browser a colour it can use to customise the display of the page and surrounding elements such as the address bar. The reason we have a meta tag solution to this rather than leaving it to CSS is performance; it allows the browser to make the relevant updates immediately.
In practice you might specify your “highlight colour”—Clearleft currently use their signature green. Alternatively you could specify both your light theme and dark theme background colours, using the media
attribute to associate each with the user colour scheme preference it is optimised for. This is the approach I chose.
<!-- Set theme color to white when user prefers light mode -->
<meta name="theme-color" content="rgb(255,255,255)" media="(prefers-color-scheme: light)">
<!-- Set theme color to a custom black when user prefers dark mode -->
<meta name="theme-color" content="rgb(38,38,38)" media="(prefers-color-scheme: dark)">
The theme-color meta tag has mixed support. It works well in Safari and iOS Safari. Meanwhile Chrome only applies the colour on installed progressive web apps (PWAs).
This had me considering PWA matters again for the first time in a while. I previously definined a PWA “theme colour” for my website in my manifest.manifest
file. However unlike in the theme-color
meta tag approach, the manifest allows defining one theme colour only. While I don’t think there’d be any issue in having both the meta tag and manifest value in play simultaneously (my understanding is that the HTML would take priority over the manifest), I decided to keep things simple and removed theme_color
from my manifest file for now.
Note: the w3c manifest spec may support multiple theme colours in future.
The color-scheme meta tag
The color-scheme
meta tag is used to specify one or more color schemes/modes with which the page is compatible. Unlike theme-color
you don’t provide an arbitrary colour value, but rather one of light
or dark
or both in combination. This tells the browser whether it can change default background, foreground and text colours to match the current system appearance. As Jim Nielsen notes in Don’t forget the color-scheme property, it gives the browser permission to automatically change standard form controls, scroll bars, and other named system colors.
I include this meta tag on each page (just after my theme-color
meta tags). I indicate that my styles have been prepped to handle the browser applying either light or dark colour schemes and—via the order of values in the content attribute—that I prefer light.
<meta name="color-scheme" content="light dark">
Notes:
- I’ve also seen
<meta name="supported-color-schemes">
however Thomas Steiner explained that this is an old syntax and has since been standardised as<meta name="color-scheme">
; - There is also a
color-scheme
CSS property, which I’ll come to later. Once again though, having an HTML meta tag helps browsers to apply things faster; - although I’m currently intimating that I prefer light, in future I might update this to dark first (assuming I don’t hear of any reasons against it). The dark colour palette is really growing on me!
Ensuring colours are accessible
While working on a new dark colour palette and tweaking my light palette I made sure to check that colour contrast met accessibility requirements.
Useful tools on this front include:
- Activating Chrome’s element inspector (the “box with arrow at bottom-right” icon) enables hovering an element to check the contrast of text against background;
- Erik Kennedy’s Accessible Color Generator provides slightly modified colour alternatives when contrast is insufficient—really handy and I used this a couple of times;
- Are my colors accessible? is another great “check contrast, digest results, then tweak” tool which provides more detailed information than the Chrome inspector, especially regarding the impact of small text on contrast.
Design Tokens
It quickly became clear that I’d need global colour settings that I could use across multiple technologies and multiple files. For example, I’d want to reuse the same custom background colour value in an HTML theme-color
meta tag, in CSS, and perhaps also in a JSON-based manifest file.
I’d previously bookmarked Heydon Pickering’s article on design tokens in Eleventy and now was the time to give it a spin.
I created _data/tokens.json
in which I defined raw tokens such as colorDark
, colorDarker
and colorLight
.
Then in my Nunjucks-based HTML templates I could access that token data to define meta tag values using {{ tokens.colorLight }}
. These values would be interpolated to the real CSS colour values during build.
I also created a new Nunjucks file, theme_css.njk
, adding permalink: "css/theme.css"
in its frontmatter so that it’d generate a CSS file. This file maps my design tokens to custom properties set on the root element, something like this:
:root {
--color-dark: {{ tokens.colorDark }};
--color-darker: {{ tokens.colorDarker }};
--color-light: {{ tokens.colorLight }};
--color-grey: {{ tokens.colorGrey }};
--color-highlight: {{ tokens.colorHighlight }};
--color-highlight-dark: {{ tokens.colorHighlightDark }};
--color-highlight-darker: {{ tokens.colorHighlightDarker }};
--color-text: {{ tokens.colorText }};
}
Custom properties FTW
Using custom properties allowed me to do the hard work of preparing my light and dark themes upfront and removed the need to constantly duplicate values and write code forks throughout my CSS.
Having previously created a base layer of reusable custom property representations of my design tokens, I began creating more specific custom properties to serve different contexts.
:root {
--color-page-bg-light: var(--color-light);
--color-page-bg-dark: var(--color-dark);
--color-ink-dark: var(--color-text);
--color-ink-light: var(--color-light);
}
CSS for default and dark-mode contexts
Now to set my light-mode defaults and fork these when the user prefers dark-mode.
Here’s my CSS:
:root {
color-scheme: light;
--color-highlight: var(--color-highlight-darker);
--color-page-bg: var(--color-page-bg-light);
--color-band: var(--color-darker);
--color-ink: var(--color-ink-dark);
--color-ink-offset: var(--color-grey);
@media (prefers-color-scheme: dark) {
color-scheme: dark;
--color-highlight: var(--color-highlight-dark);
--color-page-bg: var(--color-page-bg-dark);
--color-band: var(--color-darker);
--color-ink: var(--color-ink-light);
--color-ink-offset: var(--color-grey);
}
accent-color: var(--color-highlight);
}
body {
background-color: var(--color-page-bg);
color: var(--color-ink);
}
Let’s break that down.
The color-scheme
property allows us to specify one or more colour schemes/modes with which an element is compatible. I’m defining it on the root element i.e. for the whole page. My approach was to start by specifying support for a light colour scheme only, then define background and text colour custom properties with values optimised for light colour scheme.
Using @media (prefers-color-scheme: dark) {…}
we can check the user’s colour scheme preference. When the user prefers dark, I change color-scheme
to dark
to let the browser do its “set sensible defaults” thing, and I update the custom property values to those optimised to blend with a dark appearance.
The reusable, theme-sensitive custom properties I’ve plumped for are:
- highlight colour: really just my brand colour, although lightened a touch in dark mode for better contrast;
- page background colour;
- “band” background colour: something I’d use as a band of alternative background colour to distinguish special sections like the header or footer;
- ink colour: my default text colour;
- “offset” ink colour: a grey for little bits of meta text like dates and categories.
I’m sure I’ll evolve these over time.
Lastly, the accent-colour
CSS property is used by some form controls e.g. radios and checkboxes. I set that to my highlight colour with the intention of creating a more branded experience.
SVG considerations
My SVG-based logo didn’t look right in dark mode, however the fix was pretty simple. I applied BEM-style element classes to any problematic path
elements of the SVG then wrote CSS for those. The CSS tests for prefers-color-scheme: dark
and updates the path’s fill
or stroke
colour as necessary.
What about leaving everything to the browser?
Having read Jim Nielsen’s brilliant article on CSS System Colors I tried feeding the browser color-scheme: light dark
, removing my arbitrary dark theme colours, and leaving the colour palette entirely up to the browser. Along the way I learned some interesting lessons on system colours—which we can also choose to use as valid CSS colours in our custom components—such as Canvas
and CanvasText
.
I love this idea in principle! However in practice:
- only Safari’s colour palette looks consistent with the rest of the Mac OS. The palette applied by Chrome doesn’t; it has its own, different “black”. So for the control you’ve given away, you don’t necessarily achieve that nice “consistency with the OS” reward in return;
- as far as I know you can’t lighten or darken the system colours so it’s hard to create a multi-tone, complimentary palette, and also be confident in achieving sufficient colour contrast. That’s fine if you only need a single background colour for the entire page with no alternate bands of colour (like on Jim’s blog) however I think that’s a little restrictive. (Maybe I’m getting too hung up on control here though, and introducing additional arbitrary colours would work fine alongside a variable “system black”);
- Firefox and iOS Safari are a little problematic in their support for the technologies involved, leading to writing a few hacky workarounds.
For the combined reasons above I’m sticking with manually defined colours for now (however I have a pull request I can revisit later if the mood takes).
What could I do better?
With extra time, I might:
- think more deeply about dark theme design, perhaps following some of the tips regarding shadows, depth and colour contrast in CSS Tricks’ A complete guide to dark mode on the web;
- consider providing a theme switcher control to allow the user to choose the website theme they want regardless of the current OS appearance. (But I probably won’t; I’m not sure it’s necessary)
References
- Theming, from Jeremy Keith’s excellent Learn Responsive Design
- Design Tokens in Eleventy by Heydon Pickering
- CSS System Colors by Jim Nielsen
Learn Responsive Design (on web.dev)
Jeremy Keith’s new course for Google’s web.dev learning platform is fantastic and covers a variety of aspects of responsive design including layout (macro and micro), images, icons and typography.
Images on the Web: The Big Picture, Part 1
In modern web development there are a myriad ways to present an image on a web page and it can often feel pretty baffling. In this series I step through the options, moving from basic to flexible images; then from modern responsive images to the new CSS for fitting different sized images into a common shape. By the end I’ll arrive at a flexible, modern boilerplate for images.
Scope
This article is primarily about the HTML img
element (and related markup). I might mention CSS background images at some point, but by and large I’m focusing on images as content rather than decoration.
Similarly I probably won’t mention icons at all. I see them as a separate concern and recommend you use inline SVG rather than any image-based approach.
Terminology
Replaced element
The image element is a replaced element which means that the element is replaced by the resource (file) referenced in its src
attribute.
Aspect Ratio
You get an image’s aspect ratio by dividing its width by its height.
A 160px wide × 90px tall image can be represented as 16:9, or 1.777.
Aspect Ratio is an intrinsic characteristic of an image—i.e. it is “part of the image”—therefore outside of our control as developers. We can apply extrinsic settings which change the dimensions of the rendered image on our web page, however its aspect ratio was determined when the image was originally created and cropped.
Assumptions
CSS-wise, assume that we’ll start with nothing more complex than the following boilerplate:
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
img {
border-style: none;
display: block;
}
A basic image
Let’s start by going back to basics. I can include an image on a web page like so:
<img src="/img/250x377.jpg" alt="A Visit… by Jennifer Egan" />
Note that our markup contains no width
or height
attributes, just an alt
for accessibility. With no size-related attributes (and in the absence of any CSS acting on its width or height) the image simply displays at its intrinsic dimensions i.e. the dimensions at which the file was saved, in this case 250 × 377 pixels. The image is output as follows:

Now I know that narrow and static images like this feel pretty old-school. Since the Responsive Web Design movement we’re more accustomed to seeing full-column-width media and complex markup for intelligently selecting one file from many options.
However I still occasionally encounter use cases for displaying a relatively narrow image as-is.
Sticking with the book image example, given its aspect ratio you probably wouldn’t want it to be full-column-width on anything other than the narrowest screens simply because of how tall it could become at the expense of the reading experience. You might also be loading your images from a third party bookshop with which you have an affiliate scheme, and therefore have little control over file size and other factors influencing performance and responsive behaviour. As such you might do well to keep things simple and just load a sensibly-sized thumbnail.
See also this figure illustrating a simple database schema on the Ruby on Rails Tutorial website. On wide viewports, the author’s preference is to simply display the image at its natural, small size and centre it rather than blowing it up unnecessarily.
In summary, there remain times when you might need a narrow, fixed-size image so I want to keep that option open.
Include size attributes
When we know the dimensions of our image in advance, we can improve upon our previous markup by explicitly adding the width
and height
attributes.
<img src="/img/250x377.jpg" width="250" height="377" alt="…" />
Don’t expect fireworks; this renders the image exactly as before.

However, this addition allows the browser to reserve the appropriate space in the page for the image before it has loaded. If we don’t do this, we risk a situation where text immediately after our image renders higher up the page than it should while the image is loading, only to shift jarringly after the image loads.
We’re not overriding width or height with CSS (remember CSS rules would trump HTML attributes because attributes have no specificity) so the browser will render the image at whatever size attribute values we provide, regardless of the image’s intrinsic dimensions. As such, to avoid squashing and distortion we would ensure that our size attribute values matched the image’s real dimensions.
Flexible Images
At the dawn of the mobile web, many authors sought to handle mobile devices by creating a separate, dedicated mobile website. However this meant duplication of cost, code, content and maintenance. Responsive Web Design provided a counterpoint with a “create once, publish everywhere” philosophy which embraced the fluidity of the web and suggested we could serve desktop and mobile devices alike from a single codebase.
RWD proposed making layout and content adaptable. This included the idea of flexible images—that for any given image you need just a single, wide version plus some clever CSS to enable it to work not only on desktop but also to adapt its size for narrower contexts and still look good.
By default when a wide image is rendered within a narrower container it will overflow that container, breaking the layout. The key to avoiding this is to set a tolerance for how wide the image is permitted to go. Generally, we’ll tolerate the image being 100% as wide as its container but no wider. We can achieve this using max-width
.
<img src="/img/wide.png" alt="…" />
img {
max-width: 100%;
}
The eagle-eyed will have noticed that the above snippet once again excludes the HTML width
and height
attributes. That’s because when we began working responsively many of us stopped adding those size attributes, feeling that for flexible images the practice was redundant. The image’s dimensions were now a moving target so the space needing reserved by the browser while the image loaded was variable rather than fixed. And we were right: for a long time, browsers were not capable of reserving space for a moving target, so including the size attributes served no real purpose.
Regardless, some content management systems (most notably Wordpress) continued to output images with HTML width
and height
attributes as standard. This introduced a challenge. Without the attributes we could rely on the browser to take our simple max-width:100%
declaration and also implicitly apply height:auto
thereby always preserving the image‘s aspect ratio when scaling it down. To achieve the same goal when the HTML height
attribute is present, we needed the following revised CSS:
img {
max-width: 100%;
height: auto;
}
Here’s an example of a flexible image. It’s 2000 pixels wide, but shrinks to fit inside its narrower parent. Magic!

Jank-free responsive images
There’s been a recent development wherein modern browsers can now reserve appropriate space for flexible images while they are loading (rather than only for fixed images).
This means that adding the width
and height
attributes is once again a good idea.
If you know the image’s aspect ratio in advance, you can now use any combination of width
and height
attribute values which represent that ratio, and the browser will dynamically calculate and reserve the appropriate required space in the layout while the image loads, once again avoiding those jarring layout shifts I mentioned before.
However this presents a couple of challenges.
Firstly, having the height
HTML attribute once again means that for any image we want flexibly scaled and safely constrained by CSS max-width
, we’ll also need to override that explicit height attribute value with CSS.
Secondly, having the width
attribute can be problematic when the image is one which we explicitly want to be full-container-width, such as the featured image in a blog post. The problem arises when the width
attribute value is less than the containing element’s current width. If the only CSS you have on your image is max-width:100%
then the image will adopt the value from its width
attribute and consequently be narrower than its parent, ruining the effect. One approach might be to always use a sufficiently high width
value but that feels a tad brittle; I’d rather employ a solution that is more explicit and decisive.
To solve both of the above challenges, we can apply some additional CSS.
/*
Ensure correct aspect ratio is preserved when
max-width: 100% is triggered and image
has the HTML height attribute set,
while doing no harm otherwise.
*/
img[height] {
height: auto;
}
/*
Optional class to make an image 100% container-width.
Overrides the 'width' attribute, avoiding the risk of the image
being too narrow because its width value is narrower than the container.
When using this try to ensure your image’s intrinsic width is at least as
wide as its container’s maximum width because otherwise on wide
viewports the image would stretch and the results might not be great.
*/
.u-full-parent-width {
width: 100%;
}
Pros and cons of the “one large image” approach
I’d like to quickly take stock.
Let’s say we have a source image which is 1200px wide. Let’s also say that it’s the featured image for a blog post and therefore will be placed in our main content column, and that this column is never wider than 600px.
If we make the image flexible using max-width:100%
, it’ll work on wide viewports and narrow viewports (such as a mobile phone) alike.
On the plus-side, we only need to create one image for our blog post and we’re done.
Another positive is that on devices with retina screens—capable of displaying a comparatively greater density of pixel information in the same physical space—our oversized image will appear at higher quality and look great.
On the downside, we are delivering a much larger image and therefore file size than is required for, say, a 320px wide context. This has performance implications since bigger files mean longer download times, and this is exacerbated when the device is not connected to high-speed wifi (as is often the case with a mobile phone).
Another issue which is perhaps less obvious is that your content may not be read on your website with your CSS operating on it. For example if your website has an RSS feed (like mine does) then someone may well be reading your article in another environment (such as a feed reader application or website) and who is to say how that oversized image will look there?
Dealing with these challenges using modern Responsive Images will be the subject of Part #2.
References
Jank-free Responsive Images
Here’s how to improve performance and prevent layout jank when browsers load responsive images.
Since the advent of the Responsive Web Design era many of us, in our rush to make images flexible and adaptive, stopped applying the HTML width
and height
attributes to our images. Instead we’ve let CSS handle the image, setting a width
or max-width
of 100% so that our images can grow and shrink but not extend beyond the width of their parent container.
However there was a side-effect in that browsers load text first and images later, and if an image’s dimensions are not specified in the HTML then the browser can’t assign appropriate space to it before it loads. Then, when the image finally loads, this bumps the layout – affecting surrounding elements in a nasty, janky way.
CSS-tricks have written about this several times however I’d never found a solid conclusion.
Chrome’s Performance Warning
The other day I was testing this here website in Chrome and noticed that if you don’t provide images with inline width and height attributes, Chrome will show a console warning that this is negatively affecting performance.
Based on that, I made the following updates:
- I added width and height HTML attributes to all images; and
- I changed my CSS from
img { max-width: 100%; }
toimg { width: 100%; height: auto; }
.
NB the reason behind #2 was that I found that that CSS works better with an image with inline dimensions than max-width
does.
Which dimensions should we use?
Since an image’s actual rendered dimensions will depend on the viewport size and we can’t anticipate that viewport size, I plumped for a width
of 320 (a narrow mobile width) × height
of 240, which fits with this site’s standard image aspect ratio of 4:3.
I wasn’t sure if this was a good approach. Perhaps I should have picked values which represented the dimensions of the image on desktop.
Jen Simmons to the rescue
Jen Simmons of Mozilla has just posted a video which not only confirmed that my above approach was sound, but also provided lots of other useful context.
Essentially, we should start re-applying HTML width and height attributes to our images, because in soon-to-drop Firefox and Chrome updates the browser will use these dimensions to calculate the image’s aspect ratio and thereby be able to allocate the exact required space.
The actual dimensions we provide don’t matter too much so long as they represent the correct aspect ratio.
Also, if we use the modern srcset
and sizes
syntax to offer the browser different image options (like I do on this site), so long as the different images are the same aspect ratio then this solution will continue to work well.
There’s no solution at present for the Art Direction use case – where we want to provide different aspect ratios dependent on viewport size – but hopefully that will come along next.
I just tested this new feature in Firefox Nightly 72, using the Inspector’s Network tab to set “throttling” to 2G to simulate a slow-loading connection, and it worked really well!
Lazy Loading
One thing I’m keen to test is that my newly-added inline width
and height
attributes play well with loading="lazy"
. I don’t see why they shouldn’t and in fact they should in theory all support each other well. In tests so far everything seems good, however since loading="lazy"
is currently only implemented in Chrome I should re-test images in Chrome once it adds support for the new image aspect ratio calculating feature, around the end of 2019.
A Dao of Web Design (on A List Apart)
John Allsopp’s classic article in which he looks at the medium of web design through the prism of the Tao Te Ching, and encourages us to embrace the web’s inherent flexibility and fluidity.
It’s time to throw out the rituals of the printed page, and to engage the medium of the web and its own nature.
It’s choc-full of quotable lines, but here are a few of my favourites:
We must “accept the ebb and flow of things.”
Everything I’ve said so far could be summarized as: make pages which are adaptable.
…and…
The web’s greatest strength, I believe, is often seen as a limitation, as a defect. It is the nature of the web to be flexible, and it should be our role as designers and developers to embrace this flexibility, and produce pages which, by being flexible, are accessible to all. The journey begins by letting go of control, and becoming flexible.
See all tags.