Front End Web Development

Can We Create a “Resize Hack” With Container Queries?

Css Tricks - Thu, 05/20/2021 - 4:24am

If you follow new developments in CSS, you’ve likely heard of the impending arrival of container queries. We’re going to look at the basics here, but if you’d like another look, check out Una’s “Next Gen CSS: @container” article. After we have a poke at the basics ourselves, we’re going to build something super fun with them: a fresh take on the classic CSS meme featuring Peter Griffin fussing with window blinds. ;)

So, what is a container query? It’s… exactly that. Much like we have media queries for querying things such as the viewport size, a container query allows us to query the size of a container. Based on that, we can then apply different styles to the children of said container.

What does it look like? Well, the exact standards are being worked out. Currently, though, it’s something like this:

.container { contain: layout size; /* Or... */ contain: layout inline-size; } @container (min-width: 768px) { .child { background: hotpink; } }

The layout keyword turns on layout-containment for an element. inline-size allows users to be more specific about containment. This currently means we can only query the container’s width. With size, we are able to query the container’s height.

Again, we things could still change. At the time of writing, the only way to use container queries (without a polyfill) is behind a flag in Chrome Canary (chrome://flags). I would definitely recommend having a quick read through the drafts over on

The easiest way to start playing would be to whip up a couple quick demos that sport a resizable container element.

CodePen Embed Fallback CodePen Embed Fallback

Try changing the contain values (in Chrome Canary) and see how the demos respond. These demo uses contain: layout size which doesn’t restrict the axis. When both the height and width of the containers meet certain thresholds, the shirt sizing adjusts in the first demo. The second demo shows how the axes can work individually instead, where the beard changes color, but only when adjusting the horizontal axis.

@container (min-width: 400px) and (min-height: 400px) { .t-shirt__container { --size: "L"; --scale: 2; } }

That’s what you need to know to about container queries for now. It’s really just a few new lines of CSS.

The only thing is: most demos for container queries I’ve seen so far use a pretty standard “card” example to demonstrate the concept. Don’t get me wrong, because cards are a great use case for container queries. A card component is practically the poster child of container queries. Consider a generic card design and how it could get affected when used in different layouts. This is a common problem. Many of us have worked on projects where we wind up making various card variations, all catering to the different layouts that use them.

But cards don‘t inspire much to start playing with container queries. I want to see them pushed to greater limits to do interesting things. I‘ve played with them a little in that t-shirt sizing demo. And I was going to wait until there was better browser support until I started digging in further (I’m a Brave user currently). But then Bramus shared there was a container query polyfill!

Shout out to @bramus for sharing the Container Queries polyfill by @jon_neal the other day 👏

This prompted me to get “hacking”! 😅

— Jhey 🐻🛠 (Exploring Opportunities ✨) (@jh3yy) April 30, 2021

And this got me thinking about ways to “hack” container queries.

⚠️ Spoiler alert: My hack didn’t work. It did momentarily, or at least I thought it did. But, this was actually a blessing because it prompted more conversation around container queries.

What was my idea? I wanted to create something sort of like the “Checkbox Hack” but for container queries.

<div class="container"> <div class="container__resizer"></div> <div class="container__fixed-content"></div> </div>

The idea is that you could have a container with a resizable element inside it, and then another element that gets fixed positioning outside of the container. Resizing containers could trigger container queries and restyle the fixed elements.

.container { contain: layout size; } .container__resize { resize: vertical; overflow: hidden; width: 200px; min-height: 100px; max-height: 500px; } .container__fixed-content { position: fixed; left: 200%; top: 0; background: red; } @container(min-height: 300px) { .container__fixed-content { background: blue; } }

Try resizing the red box in this demo. It will change the color of the purple box.

CodePen Embed Fallback Can we debunk a classic CSS meme with container queries?

Seeing this work excited me a bunch. Finally, an opportunity to create a version of the Peter Griffin CSS meme with CSS and debunk it!

You’ve probably seen the meme. It’s a knock on the Cascade and how difficult it is to manage it. I created the demo using cqfill@0.5.0… with my own little touches, of course. &#x1f605;

CodePen Embed Fallback

Moving the cord handle, resizes an element which in turn affects the container size. Different container breakpoints would update a CSS variable, --open, from 0 to 1, where 1 is equal to an “open” and 0 is equal to a “closed” state.

@container (min-height: 54px) { .blinds__blinds { --open: 0.1; } } @media --css-container and (min-height: 54px) { .blinds__blinds { --open: 0.1; } } @container (min-height: 58px) { .blinds__blinds { --open: 0.2; } } @media --css-container and (min-height: 58px) { .blinds__blinds { --open: 0.2; } } @container (min-height: 62px) { .blinds__blinds { --open: 0.3; } } @media --css-container and (min-height: 62px) { .blinds__blinds { --open: 0.3; } }

But…. as I mentioned, this hack isn’t possible.

Hmmm, containing inline-size shouldn’t allow block (height) queries. You’re likely hacking a bug in the js polyfill.

I love the idea, but I don’t think this approach will work using Container Queries in CSS.

— Miriam (But Terrible) (@TerribleMia) May 1, 2021

What’s great here is that it prompted conversation around how container queries work. It also highlighted a bug with the container query polyfill which is now fixed. I would love to see this “hack” work though.

Miriam Suzanne has been creating some fantastic content around container queries. The capabilities have been changing a bunch. That’s the risk of living on the bleeding edge. One of her latest articles sums up the current status.

I’ll get this fixed soon. Following the spec is CRITICAL.

BTW, I’m kinda worried about how many videos & demos already rely on the “export” behavior. The yet-published code on main drops this requirement, so `import ‘/path/to/cqfill.js’` will be all that is needed.

— Jonathan Neal (@jon_neal) May 2, 2021

Although my original demo/hack didn’t work, we can still kinda use a “resize” hack to create those blinds. Again, we can query height if we use contain: layout size. Side note: it’s interesting how we’re currently unable to use contain to query a container’s height based on resizing its child elements.

Anyway. Consider this demo:

CodePen Embed Fallback

The arrow rotates as the container is resized. The trick here is to use a container query to update a scoped CSS custom property.

.container { contain: layout size; } .arrow { transform: rotate(var(--rotate, 0deg)); } @container(min-height: 200px) { .arrow { --rotate: 90deg; } }

We‘ve kinda got a container query trick here then. The drawback with not being able to use the first hack concept is that we can’t go completely 3D. Overflow hidden will stop that. We also need the cord to go beneath the window which means the windowsill would get in the way.

But, we can almost get there.

CodePen Embed Fallback

This demo uses a preprocessor to generate the container query steps. At each step, a scoped custom property gets updated. This reveals Peter and opens the blinds.

The trick here is to scale up the container to make the resize handle bigger. Then I scale down the content to fit back where it’s meant to.

This fun demo “debunking the meme” isn’t 100% there yet, but, we’re getting closer. Container queries are an exciting prospect. And it’ll be interesting to see how they change as browser support evolves. It’ll also be exciting to see how people push the limits with them or use them in different ways.

Who know? The “Resize Hack” might fit in nicely alongside the infamous “Checkbox Hack” one day.

The post Can We Create a “Resize Hack” With Container Queries? appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

svg-loader: A Different Way to Work With External SVG

Css Tricks - Wed, 05/19/2021 - 4:28am

SVGs are awesome: they are small, look sharp on any scale, and can be customized without creating a separate file. However, there is something I feel is missing in web standards today: a way to include them as an external file that also retains the format’s customization powers.

For instance, let’s say you want to use your website’s logo stored as web-logo.svg. You can do:

<img src="/images/logo.svg" />

That’s fine if your logo is going to look the same everywhere. But in many cases, you have 2-3 variations of the same logo. Slack, for example, has two versions.

Even the colors in the main logo are slightly different.

If we had a way to customize fill color of our logo above, we could pass any arbitrary color to render all the variations.

Take the case of icons, too. You wouldn’t want to do something like this, would you?

<img src="/icons/heart-blue.svg" /> <img src="/icons/heart-red.svg" /> Load external SVGs as inline elements

To address this, I have created a library called svg-loader. Simply put, it fetches the SVG files via XHR and loads them as inline elements, allowing you to customize the properties like fill and stroke, just like inline SVGs.

For example, I have a logo on my side-project, SVGBox. Instead of creating a different file for every variation, I can have one file and customize the fill color:

CodePen Embed Fallback

I used data-src to set the URL of SVG file. The fill attribute overrides fill of the original SVG file.

To use the library, the only thing I have to ensure is that files being served have appropriate CORS headers for XHRs to succeed. The library also caches the files locally, making the subsequent much faster. Even for the first load, the performance is comparable to using <img> tags.

This concept isn’t new. svg-inject does something similar. However, svg-loader is easier to use as we only have to include the library somewhere in your code (either via a <script> tag, or in the JavaScript bundle). No extra code is needed.

Dynamically-added elements and change in attributes are also handled automatically, which ensures that it works with all web frameworks. Here’s an example in React:

But why?

This approach may feel unorthodox because it introduces a JavaScript dependency and there are already multiple ways to use SVGs, including inline and from external sources. But there’s a good case for using SVGs this way. Let’s examine them by answering the common questions.

Can we not just inline SVG ourselves?

Inlining is the simplest way to use SVGs. Just copy and paste the SVG code in the HTML. That’s what svg-loader is ultimately doing. So, why add the extra steps to load a SVG file from somewhere else? There are two major reasons:

  1. Inline SVGs make the code verbose: SVGs can be anywhere from a few lines to a few hundred. Inline SVGs can work well if what you need is just a couple of icons and they are all tiny. But it becomes a major pain if they are sizeable or many, because then, they become long strings of text in code that isn’t “business logic.” The code becomes hard to parse.

    It’s the same thing as preferring an external stylesheet over a <style> tag or using images instead of data URIs. It’s no wonder that in React codebases, the preferred approach is to use SVG as a separate component, rather than define it as a part of JSX.
  1. External SVGs are much more convenient: Copying and pasting often does the job, but external SVGs can be really convenient. Say you’re experimenting with which icon to use in your app. If you’re using inline SVGs, that means going back and forth to get the SVG code. But with external SVGs, you only have to know the name of the file.

    Take a look at this example. One of the most extensive icon repository on GitHub is Material Design Icons. With svg-loader and unpkg, we can start using any of 5,000+ icons right away.
CodePen Embed Fallback Isn’t it inefficient to trigger an HTTP request for every SVG versus making a sprite?

Not really. With HTTP2, the cost of making an HTTP request has become less relevant. Yes, there are still benefits of bundling (e.g., better compression), but for non-blocking resources and XHRs, the pros are almost non-existent in real-world scenarios.

Here’s a Pen loading 50 icons in a similar fashion as above. (Open in incognito mode as the files are cached by default):

CodePen Embed Fallback What about <use> tag (SVG symbols)?

SVG symbols separate the definition of the SVG file from its use. Instead of defining the SVG everywhere, we can have something like this:

<svg> <use xlink:href="#heart-icon" /> </svg>

The problem is that none of the browsers support using symbols files hosted on a third-party domain. Therefore, we can’t do something like this:

<svg> <use xlink:href="" /> </svg>

Safari doesn’t even support symbols files hosted on the same domain.

Can we not use a build tool that inlines the SVGs?

I couldn’t find an obvious way to fetch SVGs from a URL and inline them in common bundlers, like webpack and Grunt, although they exist for inlining SVG files stored locally. Even if a plugin that does this exists, setting up bundlers isn’t exactly straightforward. In fact, I often avoid using them until the project has reached acertain level of complexity. We must also realize that a majority of the internet is alien to things like webpack and React. Simple scripts can have a much wider appeal.

What about the <object> tag?

The <object> tag is a native way to include external SVG files that work across all the browsers.:

<object data="" width="32" height="32"></object>

However, the drawback is we’re unable to customize the SVG’s attributes unless it’s hosted on the same domain (and the <object> tag doesn’t respect CORS headers). Even if the file is hosted on the same domain, we’d require JavaScript to manipulate the fill, like this:

<object data="" width="32" height="32" onload="this.contentDocument.querySelector('svg').fill = 'red'"></object>

In short, using external SVG files this way makes it ultra-convenient to use icons and other SVG assets. As covered earlier, with unpkg, we can use any icon on GitHub without needing extra code. We can avoid creating a pipeline in a bundler to process SVG files or a component for every icon, and just host the icons on a CDN.

Loading SVG files this way packs a lot of benefits with very little cost.

The post svg-loader: A Different Way to Work With External SVG appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


QuirksBlog - Wed, 05/19/2021 - 12:35am

This week we’ll take a look at the new aspect-ratio declaration and its use. Una Kravets wrote the introductory article, but there are some additional technical points to be made. I also wrote a little fallback that you might use if you need aspect-ratio right now.

At the time of writing aspect-ratio is supported by Chrome 90, by Safari Technology Preview, and by Firefox 88 if you set the aspect-ratio flag in about:config. You need one of these browsers to see the examples below — except for the fallback, which should work in all browsers that support custom properties.

.inner-box { border: 0; outline: 1px solid black; background-size: contain; background-position: center; background-repeat: no-repeat; background-color: red; } .flex div { flex-basis: 30%; flex-grow: 1; } .ar169 { aspect-ratio: 16/9; background-image: url(/blog/pix/ar-16-9.png); } .ar916 { aspect-ratio: 9/16; flex-grow: 0; background-image: url(/blog/pix/ar-9-16.png); } .ar43 { aspect-ratio: 4 / 3; background-image: url(/blog/pix/ar-4-3.png); } .ar11 { aspect-ratio: 1 / 1; background-image: url(/blog/pix/ar-1-1.png); } output { display: block; } code { font-size: inherit; } .mincontent { height: min-content; } .fixedwidth { width: 150px; height: 100px; } .height { height: 75px; } .maxheight { max-height: 50px; } .minheight,.minheight * { min-height: 50px; } .minwidth { min-width: 100px; } .padding { padding: 10%; } .contentbox { box-sizing: content-box; } .alignitems { align-items: flex-start; } Weak declaration

aspect-ratio defines the ratio between the width and height of a box, but it is a weak declaration. If the box has a specified width and height the browser uses those values and ignores the aspect ratio. Width and height might be specified by explicit width and height declarations or by other means, as we’ll see below.


In this example all three boxes have aspect-ratio: 16/9. The first box has width: auto; height: auto; i.e. as much width as you can take, and as little height as you need. aspect-ratio takes the width, converts it to pixels, and applies the defined aspect ratio to calculate the height.

The second box has a height: 50px; width: auto. The height is strong, but the auto width is weak and allows aspect-ratio to override it. Thus the box’s width is calculated by taking the height and applying the aspect ratio.

The third box has a fixed width: 150px; height: 100px as well as an aspect-ratio: 16/9. Now both width and height are strong and the aspect ratio is ignored.


Even if aspect-ratio works fine the browser must find an integer number of device pixels for the box’s width and height. Fractions are discarded somewhere along the way. That’s why the calculated aspect ratio of a box is rarely 100% exact. In the examples you’ll often see a narrow stripe of red poking from underneath the background image. That image has the same aspect ratio as the box it appears in, but apparently uses a different calculation. In normal circumstances these tiny differences are not visible to the naked eye, so you can safely ignore them.

Fat red stripes, such as in the last box in the first example, are a sign of trouble, though.

min- and max-width and -height grid aspect-ratio and min- and max-height

You can set a min/max-width/height on the boxes. These are obeyed as normal, and aspect-ratio is obeyed as well. In this example the first box has a min-height: 100px, the second a max-height: 50px, and the third min-width: 100px. As you see they stretch up or down to their defined maximum and minimum and retain their aspect-ratio. box-sizing: border-box!

Now we come to a trickier topic unearthed originally by Ana Tudor — and this entire article is a good read that I recommend.

If it works, aspect-ratio sets either the width or the height of a box to match the other side and the defined aspect ratio. However, the exact effect depends on how width and height are defined; on box-sizing, in other words.

width may mean either the content width, without padding and border (box-sizing: content-box; the default), or the width of content + padding + border (box-sizing: border-box). In general, the latter is what we want.

aspect-ratio and box-sizing

In the next example the boxes have padding: 10%. Percentual padding is always calculated relative to the width of the parent element. Thus this box has a padding of 10% of its parent element’s width, even at the top and the bottom.

Since the padding is equal on all four sides, it may break the box’s aspect ratio. This depends on box-sizing. If you use the default box-sizing: content-box the width and height have the correct aspect ratio, but they define only the content area. An equal amoung of padding is added on all sides, and the aspect ratio is destroyed.

This problem is easy to solve: set box-sizing: border-box. Now width and height define the content, padding, and border combined, and this entire area is given the correct aspect ratio. Thus the padding is seamlessly integrated with the proper aspect ratio.

In fact, you should always set box-sizing: border-box in all your sites. content-box was a mistake, as W3C itself admitted (and as I said back in 2002). The fact that it fises aspect-ratio merely gives us an extra reason to do so.

aapect-ratio in flexbox

In a flex or grid context, aspect-ratio can appear not to work. In fact, running into these problems was what caused me to write this article in the first place.

Look at the example below. It doesn’t work! Oh noesies! What’s going on?

flex aspect-ratio

What happens here is default flexbox behaviour. First the widths of the items are calculated (here from flex-basis: 30%; grow: 1), and once that’s done the height of all items is set. These heights are calculated by applying their aspect ratio, but the tallest box is used to set the height of all items in the row. In our example that is the 1/1 box, so the 16/9 and 4/3 boxes also have an aspect ratio of 1/1.

This default behaviour is ruled by align-items: stretch, which is part of the flexbox default. If you use any other value the boxes’ height is set to auto and they take their proper aspect ratio. flex-start is the most obvious choice, but see CSS Tricks’ great flexbox guide for more options.

flex aspect-ratio and align-items

If for some reason you want to overrule the height stretch on only a single item you can give that item either align-self: flex-start or any other value, or height: min-content. Both work fine. The third box in the next example has height: min-content.

flex aspect-ratio and height: min-content aspect-ratio and grid

In a grid context aspect-ratio encounters the same problems as in a flexbox environment, only with added browser bugs that I wrote about last week. I expected the same behaviour as in a flexbox context, but that’s not entirely what’s happening.

The good news is that align-items, align-self, and height: min-content all work exactly as with flexbox. They negate the default grid behaviour of stretching the height of all elements in a row to the height of the highest element.

grid aspect-ratio and align-items

The problems are in the default rendering of aspect-ratio. Chrome and Safari implement this in one incorrect way, and Firefox is a quite different, equally incorrect way.

If all boxes in the example below have the same width and height the bugs have been solved. They get the same height for the reasons I explained under flexbox — grid works the same in this respect (I hope).

grid aspect-ratio with browser bugs

Firefox obeys the aspect-ratio while I think it shouldn’t. Instead, like in the flexbox example, it should stretch the boxes to the height of the highest in the row. In addition it calculates the height of the entire grid by assuming its items have the minimum height needed for their content — and since my example boxes do not have any content that height is 0. Thus the grid container is way too small. Both are clearly bugs that will probably be fixed soon.

Chrome and Safari TP size the grid container correctly, but seem to take the height as reference and size the width accordingly instead of taking the width as reference and sizing the height. Thus the 16/9 and 4/3 items become way too wide. I think this is also a bug — if it isn’t someone will have to carefully explain to me what’s going on. Fortunately this bug goes away if you use align-items — which you’re going to want to do in any case.

A fallback

(After writing this fallback I found that Ana Tudor wrote pretty much the same one in her article. I mean, why do I even bother competing with scarily smart people like her? But I came by it independently, honest.)

p.test { --aspectRatio: 16/9; --padding: 0.5em; border: 1px solid; padding: var(--padding); aspect-ratio: var(--aspectRatio); box-sizing: border-box; box-shadow: 0.3em 0.3em 0.5em darkgray; } @supports not (aspect-ratio: 16/9) { p.test { padding: 0; padding-top: calc((1 / (var(--aspectRatio)))*100%); position: relative; } p.test > span { display: block; position: absolute; top: var(--padding); left: var(--padding); } p.test::after { content: " (aspect-ratio not supported; falling back)"; position: absolute; bottom: 0; right: var(--padding); } } function initBoxes() { let controls = document.querySelector('p.controls'); let element = document.querySelector('p.test') controls.addEventListener('click',function(e) { if ( !== 'INPUT') return; let newAspectRatio =;'--aspectRatio',newAspectRatio); //'--aspectHeight',newAspectRatio[1]); },false); window.initBoxes = function () {} }

5/1 3/1 16/9 4/3 9/16

Since browser support isn’t quite there yet we need to continue to use the old padding-top trick as a fallback, as I do in this paragraph. It is supposed to have an aspect ratio even in browsers that do not support aspect-ratio.

The core of the fallback is the following CSS. Fair warning: this solution is only lightly tested; I went from conception to successful execution in about 30 minutes — though I spent 90 more minutes on a custom property issue.

The extra <span> is ugly, but I don’t see a way around it. If your aspect-ratioed boxes do not contain text you can leave it out.

<p class="test"><span>The text</span></p> p.test { --aspectRatio: 16/9; --padding: 0.5em; border: 1px solid; padding: var(--padding); aspect-ratio: var(--aspectRatio); box-sizing: border-box; } @supports not (aspect-ratio: 16/9) { p.test { padding: 0; padding-top: calc((1 / (var(--aspectRatio)))*100%); position: relative; } p.test > span { display: block; position: absolute; top: var(--padding); left: var(--padding); } }

Store the desired aspect ratio in --aspectRatio. Set aspect-ratio to that value. If the browser doesn’t support aspect-ratio, set the padding-top to 1 / the aspect ratio as a percentage. The script that runs in this page changes the value of --aspectRatio.

The trick here is that percentual padding is calculated relative to the parent element’s width. If the box spans its parent’s entire width, you can take that width, multiply it by 1 divided by the aspect ratio (so for instance 9/16th when the aspect ratio is 16/9) and convert the result to a percentage. Now the padding stretches the box to the desired aspect ratio.

If the box has any real content we have to wrap it in an extra HTML element and give that element position: absolute so that it does not influence the box’s height. Then we place it in the box, with a top and left coordinate equal to the box’s padding. Now the text appears to flow naturally. You don’t need this trick if the box only has a background image or gradient; those ignore padding anyway.

Or you can wait a few months until all browsers support aspect-ratio. It won’t be long now.

I may write a separate article about the incredible number of brackets we need in the padding-top line.

Pinned Audio WordPress Theme

Css Tricks - Tue, 05/18/2021 - 9:14am

I’m afraid I have to start this with a whole backstory, as the journey here is the point, not so much the theme.

A fella wrote to me a while back outlining a situation he was in. His company has a bunch of WordPress sites for public radio, many of which are essentially homes for podcasts. There is one specific bit of functionality he thought would be ideal for them all: to have a “pinned” audio player. Like you could play a podcast, then continue navigating around the site without that podcast stopping.

This is somewhat tricky to pull off in WordPress, because WordPress does full page reloads like any other regular website not doing anything special with link handling or history manipulation. When a page reloads, any audio on the page stops playing. That’s just how the web works.

So how would you pull it off on a WordPress site? Well, you could make it a headless WordPress site and rebuild the entire front-end as a Single Page App. Sounds fun to me, but I’d be hesitant to make that call just for this one thing.

What else could you do? You could find a way to make the page never reload. I remember doing this on a little static site 10 years ago, but that wasn’t a full blown WordPress site and I didn’t even bother updating the URL back then.

What if you did this…

  1. Intercept internal link clicks
  2. Ajax’d the content from that URL
  3. Replaced the content on the page with that new content

I’ll do this in jQuery quick for ya:

$("a").on("click", () => { const url = $(this).attr("href"); $.get(url + " main", (data) => { $("main").html(data); history.pushState({}, "", url); }); });

That’s not far off from being literally functional. You’d wanna watch for a popstate event to deal with the back button, but that’s only a few more lines.

In this hypothetical world, you’d lay out the site like:

<html> <!-- ... --> <body> <main></main> <audio src="" controls ...></audio> </body> </html>

So all that <main> content gets swapped out, the URL changes, but your <audio> player is left alone to keep playing in peace. You’d write more JavaScript to give people a way to update what podcast is playing and such.

Turns out there is more to think about here though. Are any inline scripts on the content going to run? What about updating the <title> too? There are enough edge concerns you probably will get annoyed dealing with it.

I wanted to have a play with this stuff, so I tossed together a WordPress theme and reached for Turbo instead of hand-writing something. Turbo (the new version of Turbolinks) is designed just for this. It’s a JavaScript library you drop on the page (no build process, no config) and it just works. It intercepts internal link clicks, Ajax’s for new content, etc. But it has this interesting feature where if you put a data-turbo-permanent attribute on an HTML element, it will persist it across that reload. So I did that for the audio player here.

Here’s the thing though.

I just don’t have time to finish this project properly. It was fun to have a play, but my interest in it has kinda petered out. So I’ll leave it alone for now:

Live Demo GitHub Repo

It almost works, minus one glaring bug that the audio stops playing on the first navigation, then works after that. I’m sure it’s fixable, but I just don’t have much skin in this game. I figure I’ll just bow out and leave this code around for someone to pick up if it’s useful for them.

Another thing at play here is that Turbo is from Basecamp, and Basecamp has rather imploded recently making it not feel great to be using their software. Exacerbated by the fact that Sam Stephenson wrote 75% of Turbo and has said he won’t be touching it (or other related projects) unless the software is moved to its own foundation. Turbo was already in a shaky place since it seemed buggy compared to Turbolinks, and now is on very gnarly ground.

The post Pinned Audio WordPress Theme appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

A Love Letter to HTML & CSS

Css Tricks - Mon, 05/17/2021 - 4:27am

Ashley Kolodziej — May 2021


I see you.

In the back there, behind JavaScript and React and PHP and all those “real” programming languages, I see you. And I appreciate you.

I’ve seen the YouTube videos. You’ve been condensed down to a sixty-second blip on the path to bigger and better things, a one-trick div pony at the back of the race. You’re a support character. Everyone knows HTML these days. Even if that’s not the case, it’s not hard to learn, they say.

I know it’s not true.

You are the foundation of the Internet. You are the bridge between humans and information. When we say HTML isn’t an expertise in and of itself, when we take you for granted, we leave behind the people and systems who access that information using web crawlers and accessibility technology.

They say you’re not a real programming language like the others, that you’re just markup, and technically speaking, I suppose that’s right. Technically speaking, JavaScript and PHP are scripting languages. I remember when it wasn’t cool to know JavaScript, when it wasn’t a “real” language too. Sometimes, I feel like these distinctions are meaningless, like we built a vocabulary to hold you (and by extension, ourselves as developers) back. You, as a markup language, have your own unique value and strengths. Knowing how to work with you best is a true expertise, one that is too often overlooked.

Markup requires systematic thinking. What structure is the best match for this content? How can we make this content easier to discover and parse in the right order? What tags do we need to ensure a screen reader will parse your information correctly? I want you to know I know how important you are, and I still ask these questions.

I think of you every time I test a website in VoiceOver and discover it is completely unusable, with my keyboard’s focus jumping away to places I can’t actually interact with and no clear sectioning and headings to help navigate.

And to my longtime friend, CSS. I want you to know I understand you are so much more than just a pretty face. Sure, your main job is to, well, style markup. But why should that be any less celebrated than the other languages? You are the visual translation of information. What good is all the information in the world if we can’t easily understand it? You hold the keys to hierarchy and contrast and layout, the keys to visual communication.

Your language is an art. I recognize your importance, the balance of performance in rendering and specificity and predicting when and where other systems or designers might want to override something. Sure, you style, and you style well. But what the world forgets sometimes is you are, at heart, a planner: the cascading part of Cascading Style Sheets. Oh, to be JavaScript where you can do whatever you want, whenever you want, and change markup and styles on the fly. Don’t they know inline styles are some of the most specific styles around?

I know, and I respect that. There is a time and place for specificity, and I cherish your ability to manage that. I love your system of overrides, of thinking ahead to what should and shouldn’t be modifiable by another developer easily. I find the appreciation of specificity and !important and contrast and all the beautiful little things you do well increasingly lost in the pursuit of the newest and shiniest frameworks.

But I am still here for you, HTML and CSS. And I will continue to show everyone I can how much you both have to offer. Because without that foundational care and expertise, we wouldn’t be able to communicate this information at all. You are the languages at the core of equitable information distribution, and I want you to know that even if you aren’t in the spotlight right now, I remember that.

Even if it sometimes feels I’m the only person out there who still does.

With love, Ashley Kolodziej const paths = document.querySelectorAll('#Layer_1 path'); let xx = 0; function recursiveCursive( path ) { const animateTo = path.getTotalLength(); = animateTo; = -animateTo; path.ontransitionend = () => { xx++; if( undefined !== paths[xx] ) { recursiveCursive( paths[xx] ); } }; } const checkHandwriting = function (entries) { const [entry] = entries; if ( entry.isIntersecting ) { recursiveCursive(paths[xx]); observer.unobserve(handwriting); } }; let observer = new IntersectionObserver(checkHandwriting, { threshold: 0.5 }); let handwriting = document.querySelector('#Layer_1'); observer.observe(handwriting);

The post A Love Letter to HTML & CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

DevTools for CSS layouts 2021 edition

Css Tricks - Thu, 05/13/2021 - 1:22pm

Chen Hui Jing covers some recent movement in DevTools:

Firefox’s grid inspector was pretty full-featured from the get-to and released together with CSS grid in Firefox 52. It was constantly improved upon since. Chrome added a basic grid inspector tool in Chrome 62 that let developers highlight elements using grid layout, but more robust features were only added in Chrome 87. And now, Webkit [sic] has joined the party, as Safari Technology Preview 123 adds Grid inspecting features as well.

You love to see it. DevTools have a massive impact on how front-end developers think about, build, and of course, debug websites. Stuff like seeing the numbered grid lines visually is a huge deal. I’ve done enough mentally counting what rows/columns I want to place things on, thank you very much.

Direct Link to ArticlePermalink

The post DevTools for CSS layouts 2021 edition appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

DevTools for CSS layouts 2021 edition

Css Tricks - Thu, 05/13/2021 - 1:22pm

Chen Hui Jing covers some recent movement in DevTools:

Firefox’s grid inspector was pretty full-featured from the get-to and released together with CSS grid in Firefox 52. It was constantly improved upon since. Chrome added a basic grid inspector tool in Chrome 62 that let developers highlight elements using grid layout, but more robust features were only added in Chrome 87. And now, Webkit [sic] has joined the party, as Safari Technology Preview 123 adds Grid inspecting features as well.

You love to see it. DevTools have a massive impact on how front-end developers think about, build, and of course, debug websites. Stuff like seeing the numbered grid lines visually is a huge deal. I’ve done enough mentally counting what rows/columns I want to place things on, thank you very much.

Direct Link to ArticlePermalink

The post DevTools for CSS layouts 2021 edition appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

2021 Design Systems (Survey/Courses)

Css Tricks - Thu, 05/13/2021 - 12:55pm

My friends at Sparkbox are doing a survey on design systems, as they do each year. Go ahead and fill it out if you please. Here are the results from last year. In both 2019 and 2020, the vibe was that design systems (both as an idea and as a real thing that real companies really use) are maturing. But still, it was only a quarter of folks who said their actual design system was mature. I wonder if that’ll go up this year.

In my circles, “design system” isn’t the buzzword it was a few years ago, but it doesn’t mean it’s less popular. If anything, they are more popular almost entering the territory of assumed, like responsive design is. I do feel like if you’re building a website from components, well, you’ve got a component library at least, which is how I think of design systems (as a dude who pretty much exclusively works on websites).

I’d better be careful though. I know design systems mean different things to different people. Speaking of which, I’d be remiss if I didn’t also shout out the fact that Ethan has a handful of totally free courses he’s created on design systems.

As you might have guessed from the titles, we’ve broadly organized the courses around roles: whether you’re a designer, a developer, or a product manager, we’ve got something for you. Each course focuses on what I think are the fundamentals of design systems work, so I’ve designed them to be both high-level and packed with information

The post 2021 Design Systems (Survey/Courses) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

2021 Design Systems (Survey/Courses)

Css Tricks - Thu, 05/13/2021 - 12:55pm

My friends at Sparkbox are doing a survey on design systems, as they do each year. Go ahead and fill it out if you please. Here are the results from last year. In both 2019 and 2020, the vibe was that design systems (both as an idea and as a real thing that real companies really use) are maturing. But still, it was only a quarter of folks who said their actual design system was mature. I wonder if that’ll go up this year.

In my circles, “design system” isn’t the buzzword it was a few years ago, but it doesn’t mean it’s less popular. If anything, they are more popular almost entering the territory of assumed, like responsive design is. I do feel like if you’re building a website from components, well, you’ve got a component library at least, which is how I think of design systems (as a dude who pretty much exclusively works on websites).

I’d better be careful though. I know design systems mean different things to different people. Speaking of which, I’d be remiss if I didn’t also shout out the fact that Ethan has a handful of totally free courses he’s created on design systems.

As you might have guessed from the titles, we’ve broadly organized the courses around roles: whether you’re a designer, a developer, or a product manager, we’ve got something for you. Each course focuses on what I think are the fundamentals of design systems work, so I’ve designed them to be both high-level and packed with information

The post 2021 Design Systems (Survey/Courses) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

SVGator 3.0 Reshapes the Way You Create and Animate SVG With Extensive New Features

Css Tricks - Thu, 05/13/2021 - 4:24am

Building animations can get complicated, particularly compelling animations where you aren’t just rolling a ball across the screen, but building a scene where many things are moving and changing in concert. When compelling animation is the goal, a timeline GUI interface is a long-proven way to get there. That was true in the days of Flash (RIP), and still true today in every serious video editing tool.

But what do we have on the web for animating vector graphics? We have SVGator, an innovative and unique SVG animation creator that doesn’t require any coding skills! With the new SVGator 3.0 release it becomes a ground-breaking tool for building web animations in a visually intuitive way.

It’s worth just looking at the homepage to see all the amazing animations built using the tool itself. A powerful tool right at your fingertips

I love a good browser-based design tool. All my projects in the cloud, waiting for me no matter where I roam. Here’s my SVGator project as I was working on a basic animation myself:

Users of any design tool should feel at home here. It comes with an intuitive interface that rises to the demands of a professional workflow with options that allow more control over your workspace. You will find a list of your elements and groups on the left side, along with the main editing tools right at the top. All the properties can be found on the right side and you can bring any elements to life by setting up keyframes on the timeline.

I’ll be honest: I’ve never really used SVGator before, I read zero documentation, and I was able to fiddle together these animated CSS-Tricks stars in just a few minutes:

See that animation above?

  • It was output as a single animation-stars.svg document I was able to upload to this blog post without any problem.
  • Uses only CSS inside the SVG for animation. There is a JavaScript-animation output option too for greater cross-browser compatibility and extra features (e.g. start animation only when it enters the view or start the animation on click!)
  • The final file is just 5 KB. Tiny!
  • The SVG, including the CSS animation bits, are pre-optimized and compressed.
  • And again, it only took me a couple minutes to figure out.

Imagine what you could do if you actually had design and animation talent.

Intuitive and useful export options Optimized SVG output SVGator is also an outstanding vector editor

With the 3.0 release, SVGator is no longer just an animation tool, but a full-featured SVG creator! This means that there’s no need for a third-party vector editing tool, you can start from scratch in SVGator or edit any pre-existing SVGs that you’ve ever created. A Pen tool with fast editing options, easily editable shapes, compound options, and lots of other amazing features will assist you in your work.

That’s particularly useful with morphing tweens, which SVGator totally supports!

Here’s my one word review:

The all-new interface in SVGator has a sidebar of collapsible panels for controlling all aspects of the design. See here how easy this animation was to make by controlling a line of text, the transforms, and the filters, and then choosing keyframes for those things below.

Suitable plans for everyone

If you want to use SVGator as a vector editing tool, that’s absolutely free, which means that you can create and export an endless number of static vector graphics, regardless of your subscription plan In fact, even on the Free plan, you export three animations per month. It’s just when you want more advanced animation tooling or unlimited animated exports per month than that you have to upgrade to a paid plan.

Go Try SVGator

The post SVGator 3.0 Reshapes the Way You Create and Animate SVG With Extensive New Features appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Making Disabled Buttons More Inclusive

Css Tricks - Wed, 05/12/2021 - 4:31am

Let’s talk about disabled buttons. Specifically, let’s get into why we use them and how we can do better than the traditional disabled attribute in HTML (e.g. <button disabled> ) to mark a button as disabled.

There are lots of use cases where a disabled button makes a lot of sense, and we’ll get to those reasons in just a moment. But throughout this article, we’ll be looking at a form that allows us to add a number of tickets to a shopping cart.

This is a good baseline example because there’s a clear situation for disabling the “Add to cart” button: when there are no tickets to add to the cart.

But first, why disabled buttons?

Preventing people from doing an invalid or unavailable action is the most common reason we might reach for a disabled button. In the demo below, we can only add tickets if the number of tickets being added to the cart is greater than zero. Give it a try:

CodePen Embed Fallback

Allow me to skip the code explanation in this demo and focus our attention on what’s important: the “Add to cart” button.

<button type="submit" disabled="disabled"> Add to cart </button>

This button is disabled by the disabled attribute. (Note that this is a boolean attribute, which means that it can be written as disabled or disabled="disabled".)

Everything seems fine… so what’s wrong with it?

Well, to be honest, I could end the article right here asking you to not use disabled buttons because they suck, and instead use better patterns. But let’s be realistic: sometimes disabling a button is the solution that makes the most sense.

With that being said, for this demo purpose, we’ll pretend that disabling the “Add to cart” button is the best solution (spoiler alert: it’s not). We can still use it to learn how it works and improve its usability along the way.

Types of interactions

I’d like to clarify what I mean by disabled buttons being usable. You may think, If the button is disabled, it shouldn’t be usable, so… what’s the catch? Bear with me.

On the web, there are multiple ways to interact with a page. Using a mouse is one of the most common, but there are others, like sighted people who use the keyboard to navigate because of a motor impairment.

Try to navigate the demo above using only the Tab key to go forward and Tab + Shift to go backward. You’ll notice how the disabled button is skipped. The focus goes directly from the ticket input to the “dummy terms” link.

Using the Tab key, it changes the focus from the input to the link, skipping the “Add to cart” button.

Let’s pause for a second and recap the reason that lead us to disable the button in the first place versus what we had actually accomplished.

It’s common to associate “interacting” with “clicking” but they are two different concepts. Yes, click is a type of interaction, but it’s only one among others, like hover and focus.

In other words…

All clicks are interactions, but not all interactions are clicks.

Our goal is to prevent the click, but by using disabled, we are preventing not only the click, but also the focus, which means we might be doing as much harm as good. Sure, this behavior might seem harmless, but it causes confusion. People with a cognitive disability may struggle to understand why they are unable to focus the button.

In the following demo, we tweaked the layout a little. If you use a mouse, and hover over the submit button, a tooltip is shown explaining why the button is disabled. That’s great!

But if you use only the keyboard, there’s no way of seeing that tooltip because the button cannot be focused with disabled. The same thing happens in touch devices too. Ouch!

CodePen Embed Fallback Using the mouse, the tooltip on the “Add to cart” button is visible on hover. But the tooltip is missing when using the Tab key.

Allow me to once again skip past the code explanation. I highly recommend reading “Inclusive Tooltips” by Haydon Pickering and “Tooltips in the time of WCAG 2.1” by Sarah Higley to fully understand the tooltip pattern.

ARIA to the rescue

The disabled attribute in a <button> is doing more than necessary. This is one of the few cases I know of where a native HTML attribute can do more harm than good. Using an ARIA attribute can do a better job, allowing us to add not only focus on the button, but do so consistently to create an inclusive experience for more people and use cases.

The disabled attribute correlates to aria-disabled="true". Give the following demo a try, again, using only the keyboard. Notice how the button, although marked disabled, is still accessible by focus and triggers the tooltip!

CodePen Embed Fallback Using the Tab key, the “Add to cart” button is focused and it shows the tooltip.

Cool, huh? Such tiny tweak for a big improvement!

But we’re not done quite yet. The caveat here is that we still need to prevent the click programmatically, using JavaScript.

elForm.addEventListener('submit', function (event) { event.preventDefault(); /* prevent native form submit */ const isDisabled = elButtonSubmit.getAttribute('aria-disabled') === 'true'; if (isDisabled || isSubmitting) { // return early to prevent the ticket from being added return; } isSubmitting = true; // ... code to add to cart... isSubmitting = false; })

You might be familiar with this pattern as a way to prevent double clicks from submitting a form twice. If you were using the disabled attribute for that reason, I’d prefer not to do it because that causes the temporary loss of the keyboard focus while the form is submitting.

The difference between disabled and aria-disabled

You might ask: if aria-disabled doesn’t prevent the click by default, what’s the point of using it? To answer that, we need to understand the difference between both attributes:

Feature / Attributedisabledaria-disabled="true"Prevent click✅❌Prevent hover❌❌Prevent focus✅❌Default CSS styles✅❌Semantics✅✅

The only overlap between the two is semantics. Both attributes will announce that the button is indeed disabled, and that’s a good thing.

Contrary to the disabled attribute, aria-disabled is all about semantics. ARIA attributes never change the application behavior or styles by default. Their only purpose is to help assistive technologies (e.g. screen readers) to announce the page content in a more meaningful and robust way.

So far, we’ve talked about two types of people, those who click and those who Tab. Now let’s talk about another type: those with visual impairments (e.g. blindness, low vision) who use screen readers to navigate the web.

People who use screen readers, often prefer to navigate form fields using the Tab key. Now look an how VoiceOver on macOS completely skips the disabled button.

The VoiceOver screen reader skips the “Add to cart” button when using the Tab key.

Once again, this is a very minimal form. In a longer one, looking for a submit button that isn’t there right away can be annoying. Imagine a form where the submit button is hidden and only visible when you completely fill out the form. That’s what some people feel like when the disabled attribute is used.

Fortunately, buttons with disabled are not totally unreachable by screen readers. You can still navigate each element individually, one by one, and eventually you’ll find the button.

The VoiceOver screen reader is able to find and announce the “Add to cart” button.

Although possible, this is an annoying experience. On the other hand, with aria-disabled, the screen reader will focus the button normally and properly announce its status. Note that the announcement is slightly different between screen readers. For example, NVDA and JWAS say “button, unavailable” but VoiceOver says “button, dimmed.”

The VoiceOver screen reader can find the “Add to cart” button using Tab key because of aria-disabled.

I’ve mapped out how both attributes create different user experiences based on the tools we just used:

Tool / Attributedisabledaria-disabled="true"Mouse or tapPrevents a button click.Requires JS to prevent the click.TabUnable to focus the button.Able to focus the button.Screen readerButton is difficult to locate.Button is easily located.

So, the main differences between both attributes are:

  • disabled might skip the button when using the Tab key, leading to confusion.
  • aria-disabled will still focus the button and announce that it exists, but that it isn’t enabled yet; the same way you might perceive it visually.

This is the case where it’s important to acknowledge the subtle difference between accessibility and usability. Accessibility is a measure of someone being able to use something. Usability is a measure of how easy something is to use.

Given that, is disabled accessible? Yes. Does it have a good usability? I don’t think so.

Can we do better?

I wouldn’t feel good with myself if I finished this article without showing you the real inclusive solution for our ticket form example. Whenever possible, don’t use disabled buttons. Let people click it at any time and, if necessary, show an error message as feedback. This approach also solves other problems:

  • Less cognitive friction: Allow people to submit the form at any time. This removes the uncertainty of whether the button is even disabled in the first place.
  • Color contrast: Although a disabled button doesn’t need to meet the WCAG 1.4.3 color contrast, I believe we should guarantee that an element is always properly visible regardless of its state. But that’s something we don’t have to worry about now because the button isn’t disabled anymore.
CodePen Embed Fallback Final thoughts

The disabled attribute in <button> is a peculiar case where, although highly known by the community, it might not be the best approach to solve a particular problem. Don’t get me wrong because I’m not saying disabled is always bad. There are still some cases where it still makes sense to use it (e.g. pagination).

To be honest, I don’t see the disabled attribute exactly as an accessibility issue. What concerns me is more of a usability issue. By swapping the disabled attribute with aria-disabled, we can make someone’s experience much more enjoyable.

This is yet one more step into my journey on web accessibility. Over the years, I’ve discovered that accessibility is much more than complying with web standards. Dealing with user experiences is tricky and most situations require making trade-offs and compromises in how we approach a solution. There’s no silver bullet for perfect accessibility.

Our duty as web creators is to look for and understand the different solutions that are available. Only then we can make the best possible choice. There’s no sense in pretending the problems don’t exist.

At the end of the day, remember that there’s nothing preventing you from making the web a more inclusive place.


Still there? Let me mention two last things about this demo that I think are worthy:

1. Live Regions will announce dynamic content

In the demo, two parts of the content changed dynamically: the form submit button and the success confirmation (“Added [X] tickets!”).

These changes are visually perceived, however, for people with vision impairments using screen readers, that just ain’t the reality. To solve it, we need to turn those messages into Live Regions. Those allow assistive technologies to listen for changes and announce the updated messages when they happen.

There is a .sr-only class in the demo that hides a <span> containing a loading message, but allows it to be announced by screen readers. In this case, aria-live="assertive" is applied to the <span> and it holds a meaningful message after the form is submitting and is in the process of loading. This way, we can announce to the user that the form is working and to be patient as it loads. Additionally, we do the same to the form feedback element.

<button type="submit" aria-disabled="true"> Add to cart <span aria-live="assertive" class="sr-only js-loadingMsg"> <!-- Use JavaScript to inject the the loading message --> </span> </button> <p aria-live="assertive" class="formStatus"> <!-- Use JavaScript to inject the success message --> </p>

Note that the aria-live attribute must be present in the DOM right from the beginning, even if the element doesn’t hold any message yet, otherwise, Assistive Technologies may not work properly.

Form submit feedback message being announced by the screen reader.

There’s much more to tell you about this little aria-live attribute and the big things it does. There are gotchas as well. For example, if it is applied incorrectly, the attribute can do more harm than good. It’s worth reading “Using aria-live” by Ire Aderinokun and Adrian Roselli’s “Loading Skeletons” to better understand how it works and how to use it.

2. Do not use pointer-events to prevent the click

This is an alternative (and incorrect) implementation that I’ve seen around the web. This uses pointer-events: none; in CSS to prevent the click (without any HTML attribute). Please, do not do this. Here’s an ugly Pen that will hopefully demonstrate why. I repeat, do not do this.

Although that CSS does indeed prevent a mouse click, remember that it won’t prevent focus and keyboard navigation, which can lead to unexpected outcomes or, even worse, bugs.

In other words, using this CSS rule as a strategy to prevent a click, is pointless (get it?). ;)

The post Making Disabled Buttons More Inclusive appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

I could build this during the weekend

Css Tricks - Tue, 05/11/2021 - 11:04am

How many times have you heard that (or even uttered it under your own breath)? I know I’ve heard it in conversations. I also know I’ve wondered the same thing about a product or two — hey, the idea here is super simple, let’s get a couple buddies together and make the same thing, only better!

I like João’s take here. He reminds us that the core use case of an app or SaaS is often as easy as it sounds. But it’s the a lack of “second-order thinking” that prevents from understanding the complexities behind the scenes:

  • Was the team short-staffed and forced to make concessions?
  • Was the project managed in a waterfall, preventing some ideas from making it into the release?
  • Was there a time constraint that influenced the direction of the project?
  • Was the budget just not there to afford a specific feature?
  • Was there disharmony on the team?

There’s so much that we don’t see behind the product. João articulates this so clearly when he explains why a company like Uber needs hundreds of mobile app developers. They’re not there to support the initial use case; they’re charged with solving second-order factors and hopefully in a way that keeps complexity at a minimum while scaling with the rest of the system.

The world is messy. As software is more ubiquitous, we’re encoding this chaos in 1’s and 0’s. It’s more than that. Some scenarios are more difficult to encode in software than their pre-digital counterparts. A physical taxi queue at the airport is quite simple to understand. There’s no GPS technology involved, no geofencing. A person and a car can only be in one place at a time. In the digital world, things get messier.

I’m reminded of a post that Chris wrote up a while back where he harps on a seemingly simple Twitter feature request:

Why can’t I edit my tweets?! Twitter should allow that.

It’s the same deal. Features are complicated. Products are complicated. Yes, it would be awesome if this app had one particular feature or used some slick framework to speed things up — but there’s always context and second-order thinking to factor in before going straight into solution mode. Again, João says it much better than I’m able to:

It’s easy to oversimplify problems and try new, leaner technologies that optimize for our use cases. However, when scaling it to the rest of the organization, we start to see the dragons. People that didn’t build their software the right way. Entire tech stacks depending on libraries that teams can’t update due to reasons™. Quickly, we start realizing that our lean way of doing things may not serve most situations.

Speaking for myself at least, it’s tempting to jump straight into a solution or some conclusion. We’re problem solvers, right? That’s what we’re paid to do! But that’s where the dragons João describes come into view. There’s always more to the question (and the answer to it). Unless we slay those dragons, we risk making false assumptions and, ultimately, incorrect answers to seemingly simple things.

As always, front-end developers are aware. That includes being aware of second-order considerations.

Direct Link to ArticlePermalink

The post I could build this during the weekend appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Next Gen CSS: @container

Css Tricks - Tue, 05/11/2021 - 4:36am

Chrome is experimenting with @container, a property within the CSS Working Group Containment Level 3 spec being championed by Miriam Suzanne of Oddbird, and a group of engineers across the web platform. @container brings us the ability to style elements based on the size of their parent container.

The @container API is not stable, and is subject to syntax changes. If you try it out on your own, you may encounter a few bugs. Please report those bugs to the appropriate browser engine!

Bugs: Chrome | Firefox | Safari

You can think of these like a media query (@media), but instead of relying on the viewport to adjust styles, the parent container of the element you’re targeting can adjust those styles.

Container queries will be the single biggest change in web styling since CSS3, altering our perspective of what “responsive design” means.

No longer will the viewport and user agent be the only targets we have to create responsive layout and UI styles. With container queries, elements will be able to target their own parents and apply their own styles accordingly. This means that the same element that lives in the sidebar, body, or hero could look completely different based on its available size and dynamics.

@container in action CodePen Embed Fallback

In this example, I’m using two cards within a parent with the following markup:

<div class="card-container"> <div class="card"> <figure> ... </figure> <div> <div class="meta"> <h2>...</h2> <span class="time">...</span> </div> <div class="notes"> <p class="desc">...</p> <div class="links">...</div> </div> <button>...</button> </div> </div> </div>

Then, I’m setting containment (the contain property) on the parent on which I’ll be querying the container styles (.card-container). I’m also setting a relative grid layout on the parent of .card-container, so its inline-size will change based on that grid. This is what I’m querying for with @container:

.card-container { contain: layout inline-size; width: 100%; }

Now, I can query for container styles to adjust styles! This is very similar to how you would set styles using width-based media queries, using max-width to set styles when an element is smaller than a certain size, and min-width when it is larger.

/* when the parent container is smaller than 850px, remove the .links div and decrease the font size on the episode time marker */ @container (max-width: 850px) { .links { display: none; } .time { font-size: 1.25rem; } /* ... */ } /* when the parent container is smaller than 650px, decrease the .card element's grid gap to 1rem */ @container (max-width: 650px) { .card { gap: 1rem; } /* ... */ } Container Queries + Media Queries

One of the best features of container queries is the ability to separate micro layouts from macro layouts. You can style individual elements with container queries, creating nuanced micro layouts, and style entire page layouts with media queries, the macro layout. This creates a new level of control that enables even more responsive interfaces.

Here’s another example that shows the power of using media queries for macro layout (i.e. the calendar going from single-panel to multi-panel), and micro layout (i.e. the date layout/size and event margins/size shifting), to create a beautiful orchestra of queries.

CodePen Embed Fallback Container Queries + CSS Grid

One of my personal favorite ways to see the impact of container queries is to see how they work within a grid. Take the following example of a plant commerce UI:

No media queries are used on this website at all. Instead, we are only using container queries along with CSS grid to display the shopping card component in different views.

In the product grid, the layout is created with grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));. This creates a layout that tells the cards to take up the available fractional space until they hit 230px in size, and then to flow to the next row. Check out more grid tricks at

Then, we have a container query that styles the cards to take on a vertical block layout when they are less than 350px wide, and shifts to a horizontal inline layout by applying display: flex (which has an inline flow by default).

@container (min-width: 350px) { .product-container { padding: 0.5rem 0 0; display: flex; } /* ... */ }

This means that each card owns its own responsive styling. This yet another example of where you can create a macro layout with the product grid, and a micro layout with the product cards. Pretty cool!


In order to use @container, you first need to create a parent element that has containment. In order to do so, you’ll need to set contain: layout inline-size on the parent. You can use inline-size since we currently can only apply container queries to the inline axis. This prevents your layout from breaking in the block direction.

Setting contain: layout inline-size creates a new containing block and new block formatting context, letting the browser separate it from the rest of the layout. Now, we can query!


Currently, you cannot use height-based container queries, using only the block axis. In order to make grid children work with @container, you’ll need to add a wrapper element. Despite this, adding a wrapper lets you still get the effects you want.

Try it out

You can experiment with the @container property in Chromium today, by navigating to: chrome://flags in Chrome Canary and turning on the #experimental-container-queries flag.

The post Next Gen CSS: @container appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Distributed Persistent Rendering (DPR)

Css Tricks - Tue, 05/11/2021 - 4:33am

Like Jamstack, Netlify is coining this term.

If your reaction is: great, a new thing I need to know about and learn, know that while Distributed Persistent Rendering (DPR) does involve some new things, this is actually a push toward simplification and leverages ideas as old as the web is, just like Jamstack.

It’s probably helpful to hear it right from Matt Biilmann, CEO of Netlify:

In that short video, he makes the point that React started out very simple and solved a lot of clear problems with JavaScript architecture and, as time goes on and it tries to solve more use-cases, it’s getting a lot more complicated and risks losing the appeal it once had in its simplicity.

Jamstack, too, faces this problem. The original simplicity of it was extremely appealing, but as it grows to accommodate more use-cases, things get complicated.

One of those complications are sites with many-thousands of pages. Sites like that can have really slow build times. It’s nice to see frameworks tackle that (Google “Incremental Builds {Your Favorite Framework}”), but heck, if you change one link in a site footer, you’re re-building the whole site based on that one change.

So instead of building many-thousands of pages during a build, say you just… didn’t. Until that page is requested once, anyway. That’s DPR.

Here’s Zach Leatherman doing that. He finds a spot on his site that generates some 400 pages on each build and tells Eleventy that instead of building it during the normal build process, defer it to the cloud (literally a lambda will run and build the page when needed).

Deferring those 400 pages saves seven seconds in the build. Say your site is more dramatic, like 16,000 pages. Scratch pad math says you are saving four minutes there. It’s not just time either, although that’s a biggie. I think of all the electricity and long-term storage you save building this way.

Here’s the Netlify blog post:

Just like coining the term “Jamstack” didn’t mean inventing an entirely new architecture from scratch, naming this concept of “Distributed Persistent Rendering” doesn’t mean we’re creating a brand new solution.

The term “DPR” is new to us, but in a lot of ways, we’re taking inspiration from solutions that have worked in the past. We’re simply reworking them to fit with modern Jamstack best practices.

I like that it’s not like this entirely new thing. I’m sure Netlify’s implementation of it is no joke, but for us, it’s very easy to think about:

  • Some pages are pre-built as usual
  • Some pages are not built (deferred)
  • When the non-built pages are requested for the first time, then they are built and cached so they don’t need to be built again.

That’s it, really.

It reminds me of how old WordPress caching plugins used to work. When a page was requested for the first time it would run the PHP and MySQL queries and all that, then save the result as an .html file to the disk to serve subsequent requests. Not new, but still efficient.

The trick to a DPR architecture on Netlify is using their (beta) On-Demand Builders, so here’s the blog post that explains everything and will get you to the docs and such.

The post Distributed Persistent Rendering (DPR) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

aspect-ratio and grid

QuirksBlog - Tue, 05/11/2021 - 2:42am

I’m currently investigating the new aspect-ratio declaration and plan to write an article about it. However, I got stuck on aspect ratios in a grid context. Chrome/Safari and Firefox do something different here, and I understand neither approach. So I hope I can get some help.

aspect-ratio is currently supported by Chrome 90, by Firefox 88 with the correct flag enabled, and by Safari Technology Preview. I tested mostly in the first two — for complicated reasons I cannot install STP right now, but a kind Twitter follower sent me a few screenshots. It behaves as Chrome.

First, a general remark. aspect-ratio is intentionally a fairly weak declaration. It gives way if other constraints on boxes make the requested aspect ratio impossible. Take this example:

.my-box { width: 100px; height: 50px; aspect-ratio: 16/9; }

The box has a fixed width and height, and they overrule the aspect-ratio. The box will thus have a 2/1 aspect ratio, as dictated by its width and height, and not a 16/9 one.

.outer-box { gap: 1em; } .inner-box { border: 0; outline: 1px solid black; background-size: 90%; background-position: center; background-repeat: no-repeat; } .flex div { flex-basis: 30%; flex-grow: 1; } .ar169 { aspect-ratio: 16/9; background-image: url(/blog/pix/ar-16-9.png); } .ar43 { aspect-ratio: 4 / 3; background-image: url(/blog/pix/ar-4-3.png); } output { display: block; } .inner-box code { background: none; color: inherit; } .minheight .inner-box { min-height: 100px; } .mincontent { height: min-content; } Flexbox

With that in mind, let’s first look at aspect-ratio in a flexbox environment. I think I understand what’s going on here, and the browsers all do the same, so this is a good reference point for the grid problems we’ll encounter later.

Flex items take their width from the flexbox environment. In my example they have a flex-basis: 30%, but they could also have a width or even no width/flex-basis definition at all. In all cases the flexbox algorithm decides on the width of each item.

Once the width has been determined, it’s time for the height. Let’s assume it’s not set. In flexbox, height: auto means not “as high as you need to be for your content” but “as high as the highest box in your row.”

That is, naturally flexbox would give the boxes an equal width (because that’s what my flex declarations say) and an equal height (because that always happens in flexbox). Apparently, this counts as a set height for the aspect-ratio algorithm.

As a result the 16/9 value is ignored because the 4/3 results in a larger height, and this value is therefore the one that determines the height of the entire row.

As you see, the third box in this example does have the correct aspect ratio. That’s because it has an explicit height: min-content: set your height to whatever your content needs, and, more importantly, ignore the row height of the flex box. This, apparently, gives the aspect ratio algorithm the opening it needs to set the height to the one requested by the aspect-ratio: 16/9.

I’m not sure if my reasoning is right. I am very certain that this works in all browsers, though, so you can use height: min-content in production straight away. (max-content also works. There’s no real difference between the two in height declarations.)

flex aspect-ratio and min-content The problem: grid

Now we get to the problem: grid. To follow along, please look at the example below in Firefox 88 with the aspect-ratio flag on, and in either Chrome or Safari Technology Preview.

I expected grid to more or less behave the same as flexbox: the widths are set by the grid, the heights by the row height, and getting the proper aspect ratio would require height: min-content. That last clause is correct: the min-content trick works as it does in flexbox. It’s the behaviour of th 16/9 box without min-content that surprises me.

Here, again, the third box has height: min-content and takes the correct aspect ratio, which means not obeying the row height, in all browsers.

grid aspect-ratio and min-content

Firefox first. All boxes get their correct aspect ratio and they all have the same width, as the repeat: (3,1fr) grid template dictates. That means their height differs. More importantly, the grid container box now becomes only as high as is necessary to contain the items as they would have been without their aspect ratio.

I am 99% certain that the grid container behaviour is a bug. I am less certain whether the aspect-ratio being obeyed is also a bug.

In Chrome, the second and third box behave as expected: the last box becomes less high than the row height because of height: min-content, and the second box dictates the row height with its 4/3 aspect ratio.

But what’s up with the first box? It appears that it takes the row height as a given, but then sets the width to the value dictated by the 16/9 aspect ratio, ignoring the fact that this box now overflows its proper grid placement. Is this a bug? Or does height count for more than width in a grid context? I don’t know.

In the second example all grid items have min-height: 100px. In all browsers they they calculate their width from their aspect ratio. Thus they break the grid-defined widths. This is understandable, given that the explicit height declaration is “stronger” than the implied widths from the grid definition. (Or rather: I devoutly hope I’m right here and not talking nonsense.)

grid aspect-ratio and min-height: 100px;

Thus maybe Firefox on the one hand and Chrome/Safari on the other are not as far apart as one would think from the first grid example. Still, something is buggy in that example. I just can’t figure out what it is.

Stumped. Please help.


Css Tricks - Mon, 05/10/2021 - 11:14am

This is extremely fun. Jeff Lindsay has created Topframe, and writes:

Anybody that knows how to mess around with HTML can now mess around with their desktop computing experience. Topframe is an open source tool that lets you customize your desktop screen using HTML/CSS/JavaScript.

The default screen after installing has some hardcore weirdweb energy.

Mac-only I think for now.

But it’s super easy to customize. It’s just an index.html file at ~/.topframe/index.html. It auto-refreshes when you save the file.

oh hellllllo.

"Customize your computer screen with HTML and JavaScript"

— Chris Coyier (@chriscoyier) May 5, 2021

My first thought was to fill my desktop with everything Dave likes from his RSS feed. But I tried dropping that JavaScript in and it didn’t work. I couldn’t debug it because there is no console to look at or anything. Oh well. I imagine practical use-cases include building little widgets with live data you want to keep an eye on at all times.

The other hiccup I had is that it messes with taking window screenshots. You know how you can go Command + Shift + 4 then Space on macOS to select just one window to screenshot? You can’t do that with this running because it’s always the top window. Hence “top frame” (get it?). I think it would be more fun to be “bottom frame” in that it would act more like a background instead of being always on top.

Apparently, Windows could do this a long time ago, proving (yet again) that there is nothing new under the sun.

The post Topframe appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Links on Typography

Css Tricks - Fri, 05/07/2021 - 3:54am

A few links that I’ve been holding onto:

  • How to pick a Typeface for User Interface and App Design?” by Oliver Schöndorfer. I like the term “functional text” for everything that isn’t display or body type. Look for clearly distinct letters, open shapes, and little contrast. This reminds me of how we have the charmap screen on the Coding Fonts site, but still need to re-shoot most of the screenshots so they all have it.

  • Uniwidth typefaces for interface design by Lisa Staudinger. “Uniwidth typefaces, on the other hand, are proportionally-spaced typefaces, but every character occupies the same space across different cuts or weights.” So you can change the font-weight but the box the type occupies doesn’t change. Nice for menus! This is a different concept, but it reminds me of the Operator typeface (as opposed to Operator Mono) which “is a natural width family, its characters differing in proportion according to their weight and underlying design.”

  • Should we standardize the naming of font weights?” by Pedro Mascarenhas. As in, the literal names as opposed to font-weight in CSS where we already have names and numeric values but are at the mercy of the font. The image of how dramatically different fonts, say Gilroy Heavy and Avenir Heavy, makes the point.

  • About Legibility and Readability” by Bruno Maag. “Functional accessibility” is another good term. We can create heuristics like specific font-sizes that make for good accessibility, but all nuance is lost there. Good typography involves making type readable and legible. Generally, anyway. I realize typography is a broad world and you might be designing a grungy skateboard that is intentionally neither readable nor legible. But if you do achieve readability and legibility, it has sorts of benefits, like aesthetics and me-taking-you-seriously, but even better: accessibility.

  • Font size is useless; let’s fix it” by Nikita Prokopov. “Useless” is maybe strong since it, ya know, controls the font size. But this graphic does make the point. I found myself making that same point recently. Across typefaces, an identical font-size can feel dramatically different.

  • “The sans selection” by Tejas Bhatt. A journey from a huge selection of fonts for a long-form journalism platform down to just a few, then finally lands on Söhne. I enjoyed all of the very practical considerations like (yet again) a tall x-height, not-too-heavy, and even price (although the final selection was among the most costly of the bunch).

  • Plymouth Press” by James Brocklehurst. You don’t see many “SVG fonts” these days, even though the idea (any SVG can be a character) is ridiculously cool. This one, being all grungy, has far too many vector points to be practical on the web, but that isn’t a big factor for local design software use.

  • “Beyond Calibri: Finding Microsoft’s next default font” (I guess nobody wanted that byline). I’m so turned off by the sample graphics they chose for the blog post that I can’t bring myself to care, even though this should be super interesting to follow because of the scale of use here. The tweet is slightly better.

  • Why you should Self-Host Google Fonts in 2021″ by Gijo Varghese. I am aware of “Cache Partitioning” (my site can’t use cached fonts from your site, even if they both come from Google) but I could have seen myself trotting out the other two arguments in a discussion about this and it’s interesting to see them debunked here.

The post Links on Typography appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Can I Email…

Css Tricks - Thu, 05/06/2021 - 4:38pm

While I’m 85% certain you’ve seen and used Can I Use…, I bet there is only a 13% chance that you’ve seen and used Can I Email…. It’s the same vibe—detailed support information for web platform features—except instead of support information for web browsers, it is email clients. Campaign Monitor has maintained a guide for a long time as well, but I admit I like the design style pioneered by Can I Use….

HTML email is often joked about in how you have to code for it in such an antiquated way (<table> tags galore!) but that’s perhaps not a fair shake. Kevin Mandeville talked about how he used CSS grid (not kidding) in an email back in 2017:

Our Apple Mail audience at Litmus is approximately 30%, so a good portion of our subscriber base is able to see the grid desktop layout.

Where CSS Grid isn’t supported (and for device/window widths of less than 850 pixels), we fell back to a one-column layout.

Just like websites, right? They don’t have to look the same everywhere, as long as the experience is acceptable everywhere.

Me, I don’t do nearly as as much HTML email work as I do for-web-browsers work, but I do some! For example, the newsletter for CSS-Tricks is an RSS feed that feeds a MailChimp template. The email we send out to announce new shows for ShopTalk is similar. Both of those are pretty simple MailChimp templates that are customized with a bit of CSS.

But the most direct CSSin’ I do with HTML email is the templates for CodePen emails. We have all sorts of them, from totally custom templates, to special templates for The Spark and Challenges and, of course, transactional emails.

Those are all entirely from-scratch email templates. I’s very nice to know what kind of CSS features I can count on using. For example, I was surprised by how well flexbox is supported in email.

It’s always worth thinking about fallbacks. There is nothing stopping you from creating an email that is completely laid out with CSS grid. Some email clients will support it, and some won’t. When it is supported, a fancy layout happens. When it is not supported, assuming you leave the source order intelligible, you just get a column of blocks (which is what most emails are anyway) and should be perfectly workable.

Progressive enhancement is almost more straightforward in email where there is rarely any functionality to be concerned with.

Direct Link to ArticlePermalink

The post Can I Email… appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Apparently, You Can Use Route53 as a Blazingly Fast Database

Css Tricks - Thu, 05/06/2021 - 10:03am

Route53 is DNS management service by AWS. DNS is absolutely not a database, and yet here’s Nicholas Martin writing up some very clever trickery originally done by Corey Quinn:

When you think about it, DNS configuration is actually a very rudimentary NoSQL database. You can view and modify it at any time quite easily through your domain provider’s website, and you can view each “record” just like a row in a database table.

Many services use DNS TXT records to verify domain ownership. You would essentially add or modify a TXT record to store a key/value pair, which the service will then query.

Why? It’s super fast and costs $0.50 + $0.40 per million queries.

There are even libraries (ten34, diggydb) to help do it. I wouldn’t do it just because I’d be scared Amazon wouldn’t like it and cut it off. Plus, ya know, there isn’t exactly auth.

Direct Link to ArticlePermalink

The post Apparently, You Can Use Route53 as a Blazingly Fast Database appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

What Google’s New Page Experience Update Means for Images on Your Website

Css Tricks - Thu, 05/06/2021 - 9:57am

It’s easy to forget that, as a search engine, Google doesn’t just compare keywords to generate search results. Google knows that if people don’t enjoy their experience on a web page, they won’t stay on the page long enough to consume the content — no matter how relevant it is.

As a result, Google has been experimenting with ways to analyze the user experience of web pages using quantifiable metrics. Factoring these into its search engine rankings, it’s hoped to provide users not only with great, relevant content but with awesome user experiences as well.

Google’s soon-to-be-launched page experience update is a major step in this direction. Website owners with image-heavy websites need to be particularly vigilant to adapt to these changes or risk falling by the wayside. In this article, we’ll talk about everything you need to know regarding this update, and how you can take full advantage of it.

Note: Google introduced their plans for Page Experience in May 2020 and announced in November 2020 that it will begin rolling out in May 2021. However, Google has since delayed their plans for a gradual rollout starting mid-Jun 2021. This was done in order to give website admins time to deal with the shifting conditions brought about by the COVID-19 pandemic first.

Some Background

Before we get into the latest iteration of changes to how Google factors user experience metrics into search engine rankings, let’s get some context. In April 2020, Google made its most pivotal move in this direction yet by introducing a new initiative: core web vitals.

Core web vitals (CWV) were introduced to help web developers deal with the challenges of trying to optimize for search engine rankings using testable metrics – something that’s difficult to do with a highly subjective thing like user experience.

To do this, Google identified three key metrics (what it calls “user-centric performance metrics”). These are:

  1. LCP (Largest Contentful Paint): The largest element above the fold when a web page initially loads. Typically, this is a large featured image or header. How fast the largest content element loads plays a huge role in how fast the user perceives the overall loading speed of the page.
  2. FID (First Input Delay): The time it takes between when a user first interacts with the page and when the main thread is free for the browser to process the event. This can be clicking/tapping a button, link, or interacting with any other dynamic element. Delays when interacting with a page can obviously be frustrating to users which is why keeping FID low is crucial.
  3. Cumulative Layout Shift (CLS): This calculates the visual stability of a page when it first loads. The algorithm takes the size of the elements and the distance they move relevant to the viewport into account. Pages that load with high instability can cause miscues by users, also leading to frustrating situations.

These metrics have evolved from more rudimentary ones that have been in use for some time, such as SI (Speed Index), FCP (First Contentful Paint), TTI (Time-to-interactive), etc.

The reason this is important is because images can play a significant role in how your website’s CWVs score. For example, the LCP is more often than not an above-the-fold image or, at the very least, will have to compete with an image to be loaded first. Images that aren’t correctly used can also negatively impact CLS. Slow-loading images can also impact the FID by adding further delays to the overall rendering of the page.

What’s more, this came on the back of Google’s renewed focus on mobile-first indexing. So, not only are these metrics important for your website, but you have to ensure that your pages score well on mobile devices as well.

What’s Going to Change? Page Experience for Google Search

It’s clear that, in general, Google is increasingly prioritizing user experience when it comes to search engine rankings. Which brings us to the latest update – Google now plans to incorporate page experience as a ranking factor, starting with an early rollout in mid-June 2021.

So, what is page experience? In short, it’s a ranking signal that combines data from a number of metrics to try and determine how good or bad the user experience of a web page is. It consists of the following factors:

  • Core Web Vitals: Using the same, unchanged, core web vitals. Google has established guidelines and recommended rankings that you can find here. You need an overall “good” CWV rating to qualify for a “good” page experience score.
  • Mobile Usability: A URL must have no mobile usability errors to qualify for a “good” page experience score.
  • Security Issues: Any flagged security issues will disqualify websites.
  • HTTPS: Pages must be served via HTTPS to qualify.
  • Ad Experience: Measures to what degree ads negatively affect the user experience on your web page, for example, by being intrusive, distracting, etc.

As part of this change, Google announced its intention to include a visual indicator, or badge, that highlights web pages that have passed its page experience criteria. This will be similar to previous badges the search engine has used to promote AMP (Accelerated Mobile Pages) or mobile-friendly pages.

This official recognition will give high-performing web pages a massive advantage in the highly competitive arena that is Google’s SERPs. This visual cue will undoubtedly boost CTRs and organic traffic, especially for sites that already rank well. This feature may drop as soon as May if it passes its current trial phase.

Another bit of good news for non-AMP users is that all pages will now become eligible for Top Stories in both the browser and Google News app. Although Google states that pages can qualify for Top Stories “irrespective of its Core Web Vitals score or page experience status,” it’s hard to imagine this not playing a role for eligibility now or down the line.

Key Takeaway: What Does This Mean For Images on Your Website?

Google noted a 70% surge in consumer usage of their Lighthouse and PageSpeed Insight tools, showing that website owners are catching up on the importance of optimizing their pages. This means that standards will only become higher and higher when competing with other websites for search engine rankings.

Google has reaffirmed that, despite these changes, content is still king. However, content is more than just the text on your pages, and truly engaging and user-friendly content also consists of thoughtfully used media, the majority of which will likely be images.

With the proposed page experience badges and Top Stories eligibility up for grabs, the stakes have never been higher to rank highly with the Google Search algorithm. You need every advantage that you can get. And, as I’m about to show, optimizing your image assets can have a tangible effect on scoring well according to these metrics.

What Can You Do To Keep Up?

Before I propose my solution to help you optimize image assets for core web vitals, let’s look at why images are often detrimental to performance:

  • Images bloat the overall size of your website pages, especially if the images are unoptimized (i.e. uncompressed, not properly sized, etc.)
  • Images need to be responsive to different devices. You need much smaller image sizes to maintain the same visual quality on smaller screens.
  • Different contexts (browsers, OSs, etc.) have different formats for optimally rendering images. However, most images are still used in .JPG/.PNG format.
  • Website owners don’t always know about the best practices associated with using images on website pages, such as always explicitly specifying width/height attributes.

Using conventional methods, it can take a lot of blood, sweat, and tears to tackle these issues. Most solutions, such as manually editing images and hard-coding responsive syntax have inherent issues with scalability, the ability to easily update/adjust to changes, and bloat your development pipeline.

To optimize your image assets, particularly with a focus on improving CWVs, you need to:

  • Reduce image payloads
  • Implement effective caching
  • Speed up delivery
  • Transform images into optimal next-gen formats
  • Ensure images are responsive
  • Implement run-time logic to apply the optimal setting in different contexts

Luckily, there is a class of tools designed specifically to solve these challenges and provide these solutions — image CDNs. Particularly, I want to focus on ImageEngine which has consistently outperformed other CDNs on page performance tests I’ve conducted.

ImageEngine is an intelligent, device-aware image CDN that you can use to serve your website images (including GIFs). ImageEngine uses WURFL device detection to analyze the context your website pages are accessed from (device, screen size, DPI, OS, browser, etc.) and optimize your image assets accordingly. Based on these criteria, it can optimize images by intelligently resizing, reformatting, and compressing them.

It’s a completely automatic, set-it-and-forget-it solution that requires little to no intervention once it’s set up. The CDN has over 20 global PoPs with the logic built into the edge servers for faster across different regions. ImageEngine claims to achieve cache-hit ratios of as high as 98%+ as well as reduce image payloads by 75%+.

Step-by-Step Test + How to Use ImageEngine to Improve Page Experience

To illustrate the difference using an image CDN like ImageEngine can make, I’ll show you a practical test.

First, let’s take a look at how a page with a massive amount of image content scores using PageSpeed Insights. It’s a simple page, but consists of a large number of high-quality images with some interactive elements, such as buttons and links as well as text.

FID is unique because it relies on data from real-world interactions users have with your website. As a result, FID can only be collected “in the field.” If you have a website with enough traffic, you can get the FID by generating a Page Experience Report in the Google Console.

However, for lab results, from tools like Lighthouse or PageSpeed Insights, we can surmise the impact of blocking resources by looking at TTI and TBT.

Oh, yes, and I’ll also be focussing on the results of a mobile audit for a number of reasons:

  1. Google themselves are prioritizing mobile signals and mobile-first indexing
  2. Optimizing web pages and images assets are often most challenging for mobile devices/general responsiveness
  3. It provides the opportunity to show the maximum improvement a image CDN can provide

With that in mind, here are the results for our page:

So, as you can see, we have some issues. Helpfully, PageSpeed Insights flags the two CWVs present, LCP and CLS. As you can see, because of the huge image payload (roughly 35 MB), we have a ridiculous LCP of nearly 1 minute.

Because of the straightforward layout and the fact that I did explicitly give images width and height attributes, our page happened to be stable with a 0 CLS. However, it’s important to realize that slow loading images can also impact the perceived stability of your pages. So, even if you can’t directly improve on CLS, the faster sizable elements such as images load, the better the overall experience for real-world users.

TTI and TBT were also sub-par. It will take at least two  seconds from the moment the first element appears on the page until when the page can start to become interactive.

As you can see from the opportunities for improvement and diagnostics, almost all issues were image-related:

Setting Up ImageEngine and Testing the Results

Ok, so now it’s time to add ImageEngine into the mix and see how it improves performance metrics on the same page.

Setting up ImageEngine on nearly any website is relatively straightforward. First, go to and signup for a free trial. Just follow the simple 3-step signup process where you will need to:

  1. provide the website you want to optimize, 
  2. the web location where your images are stored, and then 
  3. copy the delivery address ImageEngine assigns to you.

The latter will be in the format of {random string} but can be updated from within the ImageEngine dashboard.

To serve images via this domain, simply go back to your website markup and update the <img> src attributes. For example:


<img src=””/>


<img src=””/>

That’s all you need to do. ImageEngine will now automatically pull your images and dynamically optimize them for best results when visitors view your website pages. You can check the official integration guides in the documentation on how to use ImageEngine with Magento, Shopify, Drupal, and more. There is also an official WordPress plugin.

Here’s the results for my ImageEngine test page once it’s set up:

As you can see, the results are nearly flawless. All metrics were improved, scoring in the green – even Speed Index and LCP which were significantly affected by the large images.

As a result, there were no more opportunities for improvement. And, as you can see, ImageEngine reduced the total page payload to 968 kB, cutting down image content by roughly 90%:


To some extent, it’s more of the same from Google who has consistently been moving in a mobile direction and employing a growing list of metrics to hone in on the best possible “page experience” for its search engine users. Along with reaffirming their move in this direction, Google stated that they will continue to test and revise their list of signals.

Other metrics that can be surfaced in their tools, such as TTFB, TTI, FCP, TBT, or possibly entirely new metrics may play even larger roles in future updates.

Finding solutions that help you score highly for these metrics now and in the future is key to staying ahead in this highly competitive environment. While image optimization is just one facet, it can have major implications, especially for image-heavy sites.

An image CDN like ImageEngine can solve almost all issues related to image content, with minimal time and effort as well as future proof your website against future updates.

The post What Google’s New Page Experience Update Means for Images on Your Website appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Syndicate content
©2003 - Present Akamai Design & Development.