Tag: query

A New Container Query Polyfill That Just Works

There is now a polyfill for Container Queries that behaves as perfectly as a polyfill should:

  1. You conditionally load it when you detect the browser doesn’t support Container Queries.
  2. You write CSS as you normally would, including current-spec-compliant Container Queries syntax code.
  3. It just works.

It’s pretty great to have a container query polyfill that is this easy to use and from Chrome itself, the first-movers as far as early test implementations. Looks like Surma put it together — thanks Surma!

There was a Container Query polyfill from Jonathan Neal called cqfill that predates this. I’m not sure if it’s officially deprecated, but it required extra non-spec CSS to work and PostCSS processing, so I’d consider it deprecated in favor of this newer polyfill.

Loading the polyfill is like this:

// Support Test const supportsContainerQueries = "container" in document.documentElement.style;  // Conditional Import if (!supportsContainerQueries) {   import("https://cdn.skypack.dev/container-query-polyfill"); }

You can pull it from npm or use as a <script>, but this way seems best to me to keep things light and easy.

Then you’re free to use the syntax for a container query in CSS. Say you have a weather widget in HTML. You’ll need an extra wrapper element for your queries. That’s just the rule: you can’t query the thing you style.

<div class="weather-wrap">   <dl class="weather">     <div>       <dt>Sunday</dt>       <dd>         <b>26°</b> 7°       </dd>     </div>     <div>       <dt>Monday</dt>       <dd>         <b>34°</b> 11°       </dd>     </div>     <!-- etc -->   </dl> </div>

The wrapper is instantiated as a container:

.weather-wrap {   container: inline-size / weather-wrapper;   /* Shorthand for: */   /* container-type: inline-size; */   /* container-name: weather-wrapper; */    /* For quick testing, do this to get a resize handle on desktop: */   /* resize: both; */   /* overflow: hidden; */ }

Then you write any global styling for that component, as well as container query scoped styles:

.weather {   display: flex; } @container weather-wrapper size(max-width: 700px) {   .weather {     flex-direction: column;   } }

Container Queries polyfill example

Here’s that slightly more fleshed-out demo of the Container Query polyfill using an actual weather widget:

I first saw this over on Bramus’ blog, and he’s got a classic card demo going with this Container Query polyfill. Scroll up and down. You’ll see a row of bear cards at the top (if your browser window is wide enough), and then similar bear cards in different layout positions below that change into nicer formats when they can, based on the container query.

Container Query polyfill browser support

The polyfill docs say:

The polyfill relies on ResizeObserverMutationObserver and :is(). Therefore, it should work in all modern browsers, specifically Chrome/Edge 88+, Firefox 78+ and Safari 14+.

There are all sorts of other minor little caveats covered in those docs, including what it does and doesn’t support. Seems like mostly niche stuff to me — the main/typical use cases are covered.

A game changer?

As I write, we’ve seen behind-flag support for Container Queries in Chrome, and it is an official spec draft now:

That’s extremely exciting and points heavily toward browsers actually shipping with Container Queries, even if the syntax changes a bit on the way (it already has a number of times). But, of course, we have no idea if/when Container Queries do ship — and when that magical threshold is crossed, we also don’t know where we can use them without much worry, like we can with flexbox and grid now.

That “just use it” date is probably a decent ways off, but if you’re into the idea of polyfilling and being careful with progressive enhancement, I’d say the date for using Container Queries could be right now-ish. Looks to me like the polyfill script comes across the wire at 2.8kb, so it’s fairly trivial in size for something so important.

I suspect this polyfill will skyrocket usage of Container Queries in this coming year.


The fact that your styles only correctly apply after a JavaScript file is downloaded and executed puts sites into Flash of Unstyled Content (FOUC) territory. Here’s a video recording where I can see it on my own demo. I’m not sure there is a way around this other than intentionally delaying rendering, which is generally considered a no-no. Similar to loading web fonts, FOUC is probably a good thing as it means your content is never hidden or delayed, even if the shifts aren’t ideal. The FOUC should go away once browser support lands and the polyfill stops loading at all.

Have fun polyfilling container queries! I’d love to see more demos of it.

GitHub Repo for the Container Query Polyfill

A New Container Query Polyfill That Just Works originally published on CSS-Tricks. You should get the newsletter and become a supporter.


, , , ,

Detecting Media Query Support in CSS and JavaScript

You can’t just do @media (prefers-reduced-data: no-preference) alone because, as Kilian Valkhof says:

[…] that would be false if either there was no support (since the browser wouldn’t understand the media query) or if it was supported but the user wanted to preserve data.

Usually @supports is the tool for this in CSS, but that doesn’t work with @media queries. Turns out there is a solution though:

@media not all and (prefers-reduced-data), (prefers-reduced-data) {   /* ... */ }

This is a somewhat complex logic puzzle involving media query syntax and how browsers evaluate these things. It’s nice you can ultimately handle a no-support fallback situation in one expression.

Direct Link to ArticlePermalink

The post Detecting Media Query Support in CSS and JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Query JSON documents in the Terminal with GROQ

JSON documents are everywhere today, but they are rarely structured the way you want them to be. They often include too much data, have weirdly named fields, or place the data in unnecessary nested objects. Graph-Relational Object Queries (GROQ) is a query language (like SQL, but different) which is designed to work directly on JSON documents. It basically lets you write queries you can quickly filter and then reformat JSON documents to get them into the most convenient shape.

GROQ was developed by Sanity.io (where it’s used as the primary query language). It’s open source and it gives us built-in ways to use it in JavaScript and the command line on any JSON source. Together, we’ll add GROQ to the terminal toolkit, which will save you time whenever you need to wrangle some JSON data on a poject.

Let’s install GROQ

Like most things, we need to install the GROQ CLI tool and can do so with with npm (or Yarn) in the terminal:

$   npm install -g groq-cli

In order to play with it, we need to have a JSON file available. We’ll use curl to download an example dataset of todo data:

$   curl -o todos.json https://jsonplaceholder.typicode.com/todos

Let’s take a quick look at a sample item in the data:

{   "userId": 1,   "id": 1,   "title": "delectus aut autem",   "completed": false },

Pretty straightforward. We have a user ID, a todo item ID, a todo title and a boolean specifying whether the todo item is completed or not.

Now let’s run a basic GROQ query: finding all completed todos, but only return the todo titles and user IDs. It’s OK to copy/paste this line because we’ll walk through what it means in a bit.

$   cat todos.json | groq '*[completed == true]{title, userId}' --pretty

The groq command line tools accept a JSON document on standard input. This works very nicely with the Unix philosophy of “doing one thing and work on a text stream.” In order to read JSON from a file we’ll use the cat command. Also note that groq will output a minimal JSON on a single line by default, but by passing --pretty, we get a nicely indented and highlighted syntax.

To store the result, we can pipe it to a new file using >:

$   cat todos.json | groq '*[completed == true]{title, userId}' > result.json

The query itself consists of three parts:

  • * refers to the dataset (i.e. the data in the JSON file).
  • [completed == true] is a filter that removes items that are marked as incomplete.
  • {title, userId} is a projection that causes the query to only return the "title" and "userId" properties.

Let’s warm up with some exercises

You probably didn’t think you’d need to exercise to get through this post! Well, the good news is that we’re only exercising the mind with a few things to try out with GROQ before we get into more details.

  1. What happens if you remove [completed == true] and/or {title, userId}?
  2. How can you change the query to find all todos by the user with an ID of 2?
  3. How can you change the query to find uncompleted todos by the user with an ID of 2?
  4. What happens if the filter in the original query example swaps places with the projection?
  5. How would you write a single command (with pipes) that downloads the JSON and processes it with GROQ?

We’ll put the answers at the end of the post for you to reference.

Querying the Nobel prize winners

The todo data is nice for a warmup, but let’s be honest: It’s not very motivating to look at a list that uses Latin as placeholder content. However, the Nobel Prize has a dataset of all past laureates available to use publicly.

Here’s a sample return:

{   "laureates": [     {       "id": "1",       "firstname": "Wilhelm Conrad",       "surname": "Röntgen",       "born": "1845-03-27",       "died": "1923-02-10",       "bornCountry": "Prussia (now Germany)",       "bornCountryCode": "DE",       "bornCity": "Lennep (now Remscheid)",       "diedCountry": "Germany",       "diedCountryCode": "DE",       "diedCity": "Munich",       "gender": "male",       "prizes": [...],     },     // ...   ] }

Ah! This is much more interesting! Let’s download the dataset and find the first name of all Norwegian laureates. Here, we’re going to use --output flag for curl to save the data to a file.

$   curl --output laureate.json http://api.nobelprize.org/v1/laureate.json $   cat laureate.json | groq '*.laureates[bornCountryCode == "NO"]{firstname}' --pretty

What do you get back? I received 12 Norwegian Nobel laureates. Not bad!

Note that this query is not quite like the first query we wrote. We have an extra .laureates in this one. When we used * in the todo dataset, it represents the whole JSON document which is contained in an array at the top-level of the todo dataset. On the other hand, the laureate file uses an object at the top-level where the list of laureates is stored in the "laureates" property.

To access a specific item, we can use the filter [0] and return just the first name. That ought to tell us who the first Norwegian was to win a Nobel prize.

$   cat laureate.json | groq '*.laureates[bornCountryCode == "NO"]{firstname}[0]' --pretty  // Returned object {   "firstname": "Ivar" }

More exercises!

We’d be remiss not to play around with this new dataset a bit to see how the queries work.

  1. Write a query to find all Nobel laureates from your own country.
  2. Write a query to return the last Norwegian laureate. Hint: -1 refers to the last item.
  3. What happens if you try to filter directly on the root object? *[bornCountryCode == "NO"]?
  4. What’s the difference between *.laureates\[bornCountryCode == "NO"\][0] and *.laureates\[0\][bornCountryCode == "NO"]?

Like last time, answers will be at the end of this post.

Working with filters

Now we know that in total there have been 12 Norwegian Nobel laureates, how many of them were born after 1950? That’s no problem figuring out with GROQ:

$   cat laureate.json | groq '*.laureates[bornCountryCode == "NO" && born >= "1950-01-01"]{firstname}' --pretty  // Sample return [   {     "firstname": "May-Britt"   },   {     "firstname": "Edvard I."   } ]

In fact, GROQ has a rich set of operators we can use inside a filter. We can compare numbers and strings with equal (==), not equal (!=), greater than (>), greater than or equal ( >=), less than (<), and less than or equal (<=). Plus, comparisons can be combined with AND (&&), OR (||) and NOT (!). The in operator even makes it possible to check for many cases at once (e.g. bornCountryCode in ["NO", "SE", "DK"]). And the defined function allows us to see if a field exists (e.g. defined(diedCountry)).

Even more exercises!

You know the drill: try playing with filters a bit to see how they work with the dataset. Answers are at the end, of course.

  1. Write a query that returns living laureates.
  2. Is there a difference between the filters [bornCountryCode == "NO"][born >= "1950-01-01"] and [bornCountryCode == "NO" && born >= "1950-01-01"]?
  3. Can you find all laureates that won a prize in 1973?

Working with projections

The Nobel Prize dataset separates the first name and the surname for each laureate, but what if we want to combine them together into one field? Projections in GROQ can do exactly that!

*.laureates[bornCountryCode == "NO" && born >= "1950-01-01"]{   "name": firstname + " " + surname,    born,   "prizeCount": count(prizes), }

Running this query tells us that May-Britt Moser and Edvard Moser received one prize (which was, in fact, the same prize):

[   {     "name": "May-Britt Moser",     "born": "1963-01-04",     "prizeCount": 1   },   {     "name": "Edvard I. Moser",     "born": "1962-04-27",     "prizeCount": 1   } ]

What happened here? Well, when we write a projection in GROQ what we’re really writing is a JSON object. Previously, we had simple projections (like {firstname}), but this is a shortcut way of writing {"firstname": firstname}. By using the expanded object syntax, we can both rename keys and transform the values.

GROQ has a rich set of operators and functions for transforming data, including string concatenation, arithmetic operators (+, -, *, /, %, **), counting arrays (count(prizes)), and rounding numbers (round(num, <amount of decimals>).


Hopefully, you’re getting a good feel for things at this point, but here are some more ways to practice working with projections:

  1. Find all laureates who have won two or more prizes.
  2. Find how many prizes have been won by women.
  3. Format a fullname key that combines lastname and firstname in the result.

Doing more at once

Watch this:

$   cat laureate.json | groq --pretty ' {   "count": count(*.laureates),   "norwegians": *.laureates[bornCountryCode == "NO"]{firstname},  } '

The result:

{   "count": 928,   "norwegians": [     {       "firstname": "Ivar"     },     {       "firstname": "Lars"     },     …   ] }

Catch that? A GROQ query doesn’t have to start with *. In this query, we’re creating a JSON object where the values are results from separate queries. This provides a lot of flexibility in what we can produce with GROQ. Maybe you want the total number of incomplete todos together with a list of the five last ones. Or maybe you want to split the todos into two separate lists: one for completed and one for incomplete. Or maybe you need to wrap everything inside an object because that is what another tool/library/framework expects. Whatever the case, GROQ has you covered.

Let’s try one last exercise. Can you project an object where laureates contains an array with a rounded percentage of the total number of prizes that each laureate has run, returning the laureates’ first name? Then, try outputting the total number handed out.


There isn’t much you need to learn before getting some good use out of GROQ. If you have followed the exercises, you’re on a great path to becoming a GROQ guru. Naturally, this introduction doesn’t touch on all the different features and aspects of GROQ, so feel free to explore the specification and the project itself on GitHub. And feel free to reach out to the team at Sanity.io if you have questions about data wrangling with GROQ.

Exercise answers

Exercise 1
Question 1

If you remove [completed == true] you will get all todos, not only those that are completed. If you remove {title, userId} you will get all properties.

Question 2
*[userId == 2]
Question 3
*[userId == 2 && completed == false] or *[userId == 2 && !completed]
Question 4

If you change the order of the filter and the projection you will do the projection first and then apply the filter. This means that you’re filtering on a list of todos that only contain title and userId, and completed == true can never be true.

Question 5
curl https://jsonplaceholder.typicode.com/todos | groq '*[completed == true]{title, userId}' > result.json
Exercise 2
Question 1
*.laureates[bornCountryCode == "INSERT-YOUR-COUNTRY-HERE"]
Question 2
*.laureates\[bornCountryCode == "NO"\][-1]
Question 3

*[bornCountryCode == "NO"] will try to filter on an object. This doesn’t make any sense, so you’ll get null as the answer.

Question 4

*.laureates\[0\][bornCountryCode == "NO"] doesn’t work as you might think. This will first find the first laureate (which happens to be Wilhelm Conrad) and then it will try to “filter” the object. This makes no sense so the answer is null.

Exercise 3
Question 1
*.laureates[died == "0000-00-00"]
Question 2

There’s no difference between the filters \[bornCountryCode == "NO"\][born >= "1950-01-01"] and [bornCountryCode == "NO" && born >= "1950-01-01"]. The first one does the filtering in two “passes” but the end result is the same.

Question 3
*.laureates["1973" in prizes[].year]
Exercise 4
Question 1
*.laureates[count(prizes) >= 2]
Question 2
count(*.laureates[gender == "female"])
Question 3
*.laureates{"fullname": surname + ", " + firstname}
Exercise 5
*.laureates{"laureates": {firstname, "percentage": round(count(prizes) / count(*.laureates[].prizes), 3) * 100}, "total": count(*.laureates[].prizes)}

The post Query JSON documents in the Terminal with GROQ appeared first on CSS-Tricks.


, , , ,

Weekly Platform News: HTML Inspection in Search Console, Global Scope of Scripts, Babel env Adds defaults Query

In this week’s look around the world of web platform news, Google Search Console makes it easier to view crawled markup, we learn that custom properties aren’t computing hogs, variables defined at the top-level in JavaScript are global to other page scripts, and Babel env now supports the defaults query — plus all of last month’s news compiled into a single package for you.

Easier HTML inspection in Google Search Console

The URL Inspection tool in Google Search Console now includes useful controls for searching within and copying the HTML code of the crawled page.

Note: The URL Inspection tool provides information about Google’s indexed version of a specific page. You can access Google Search Console at https://search.google.com/search-console.

(via Barry Schwartz)

CSS properties are computed once per element

The value of a CSS custom property is computed once per element. If you define a custom property --func on the <html> element that uses the value of another custom property --val, then re-defining the value of --val on a nested DOM element that uses --func won’t have any effect because the inherited value of --func is already computed.

html {   --angle: 90deg;   --gradient: linear-gradient(var(--angle), blue, red); }  header {   --angle: 270deg; /* ignored */   background-image: var(--gradient); /* inherited value */ }

(via Miriam Suzanne)

The global scope of scripts

JavaScript variables created via let, const, or class declarations at the top level of a script (<script> element) continue to be defined in subsequent scripts included in the page.

Note:Axel Rauschmayer calls this the global scope of scripts.”)

(via Surma)

Babel env now supports the defaults query

Babel’s env preset (@babel/preset-env) now allows you to target browserslist’s default browsers (which are listed at browsersl.ist). Note that if you don’t specify your target browsers, Babel env will run every syntax transform on your code.

{   "presets": [     [       "@babel/preset-env",       {         "targets": { "browsers": "defaults" }       }     ]   ] }

(via Nicolò Ribaudo)

All the June 2019 news that’s fit to… print

For your convenience, I have compiled all 59 news items that I’ve published throughout June into one 10-page PDF document.

Download PDF

The post Weekly Platform News: HTML Inspection in Search Console, Global Scope of Scripts, Babel env Adds defaults Query appeared first on CSS-Tricks.


, , , , , , , , , , , , ,

Revisiting prefers-reduced-motion, the reduced motion media query

Two years ago, I wrote about prefers-reduced-motion, a media query introduced into Safari 10.1 to help people with vestibular and seizure disorders use the web. The article provided some background about the media query, why it was needed, and how to work with it to avoid creating disability-triggering visual effects.

The article was informed by other people’s excellent work, namely Orde Saunders’ post about user queries, and Val Head’s article on web animation motion sensitivity.

We’re now four months into 2019, and it makes me happy to report that we have support for the feature in all major desktop browsers! Safari was first, with Firefox being a close second. Chrome was a little late to the party, but introduced it as of version 74.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Opera Firefox IE Edge Safari
74 No 63 No No 10.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
10.3 No No No No No

While Microsoft Edge does not have support for prefers-reduced-motion, it will become Chrome under the hood soon. If there’s one good thing to come from this situation, it’s that Edge’s other excellent accessibility features will (hopefully) have a good chance of being back-ported into Chrome.


While I’m happy to see some websites and web apps using the media query, I find that it’s rare to encounter it outside of places maintained by people who are active in CSS and accessibility spaces. In a way, this makes sense. While prefers-reduced-motion is relatively new, CSS features and functionality as a whole are often overlooked and undervalued. Accessibility even more so.

It’s tough to blame someone for not using a feature they don’t know exists, especially if it’s relatively new, and especially in an industry as fast-paced as ours. The deck is also stacked in terms of what the industry prioritizes as marketable, and therefore what developers pay attention to. And yet, prefers-reduced-motion is a library-agnostic feature that ties into Operating System-level functionality. I’m pretty sure that means it’ll have some significant staying power in terms of reward for time spent for skill acquisition.

Speaking of rewards, I think it’s also worth pointing out the true value prefers-reduced-motion represents: Not attracting buzzword-hungry recruiters on LinkedIn, but improving the quality of life for the people who benefit from the effect it creates. Using this media query could spare someone from having to unnecessarily endure a tremendous amount of pain for simply having the curiosity to click on a link or scroll down a page.

The people affected

When it comes to disability, many people just assume “blind people.” The reality is that disabilities are a complicated and nuanced topic, one that is surprisingly pervasive, deeply personal, and full of unfortunate misconceptions. It’s also highly variable. Different people are affected by different disability conditions in different ways — extending to a wide gamut of permanent, temporary, environmental, and situational concerns. Multiple, compounding conditions can (and do) affect individuals, and sometimes what helps one person might hinder another. It’s a difficult, but very vital thing to keep in mind.

If you have a vestibular disorder or have certain kinds of migraine or seizure triggers, navigating the web can be a lot like walking through a minefield — you’re perpetually one click away from activating an unannounced animation. And that’s just for casual browsing.

If you use the web for work, you might have no choice but to endure a web app that contains triggering animations multiple times a week, or even per day or hour. In addition to not having the autonomy to modify your work device, you may also not have the option to quickly and easily change jobs — a privilege easily forgotten when you’re a specialized knowledge worker.

It’s a fallacy to assume that a person is aware of their vestibular disorder, or what triggers it. In fact, sometimes the initial triggering experience exacerbates your sensitivity and makes other parts of a design difficult to use. Facundo Corradini shares his experience with this phenomenon in his article, “Accessibility for Vestibular Disorders: How My Temporary Disability Changed My Perspective.”

Not all assistive technology users are power users, so it’s another fallacy to assume that a person with a vestibular disorder is aware of, or has the access rights to enable a motion-reducing Operating System setting or install a browser extension.

Think of someone working in a large corporation who has to use a provisioned computer with locked-down capabilities. Or someone who isn’t fully aware of what of their tablet is capable of doing past browsing social media, watching video, and messaging their family and friends. Or a cheap and/or unorthodox device that will never support prefers-reduced-motion feature — some people purchase discontinued devices such as the Windows Phone specifically because their deprecation makes them affordable.

Do these people deserve to be hurt because of their circumstances? Of course not.

Considering what’s harmful

You can tie harm into value, the same way you can with delight. Animation intended to nudge a person towards a signup could also drive them away. This kind of exit metric is more difficult to quantify, but it definitely happens. Sometimes the harm is even intentional, and therefore an easier datapoint to capture — what you do with that information is a whole other issue.

If enough harm happens to enough people, it affects that certain something we know as branding. This effect doesn’t even need to be tied to a disability condition. Too much animation, applied to the wrong things in the wrong way will drive people away, even if they can’t precisely articulate why.

You also don’t know who might be on the receiving end, or what circumstances they’re experiencing the moment they load your website or web app. We can’t — and shouldn’t — know this kind of information, either. It could be a prospective customer, the employee at a venture capitalist firm tasked with evaluating your startup, or maybe even your new boss.

We also don’t need to qualify their relationship to us to determine if their situation is worth considering — isn’t it enough to just be proactively kind?

Animation is progressive enhancement

We also need to acknowledge that not every device that can access the web can also render animation, or render animation smoothly. When animation is used on a low-power or low quality device that “technically” supports it, the overall user experience suffers. Some people even deliberately seek this experience out as a feature.

Devices may also be set to specialized browsing modes to allow people to access your content in alternate ways. This concept is known as being robust, and is one of the four high-level principles that govern the guidelines outlining how to craft accessible experiences.

Animation might not always look the way you intend it in these modes. One example would be when the viewport is zoomed and the animation isn’t built using relative units. There’s a non-trivial chance important parts might be pushed out of the viewport, leaving the animation appearing as a random collection of flickering bits. Another example of a specialized browsing mode might be Reader Mode, where the animation may not appear at all.

Taking it to code

Considering all this, I’m wondering if there are opportunities to help web professionals become more aware of, and therefore more considerate of the downsides of poorly conceived and implemented animation.

Maybe we proactively incorporate a media query high up in the cascade to disable all animation for those who desire it, and for those who have devices that can’t support it. This can be accomplished by targeting anything where someone has expressed a desire for a low-to-no-animation experience, or any device that has a slow screen refresh rate.

The first part of the query, targeting low-to-no-animation, is done via prefers-reduced-motion. The second, targeting a screen with a low refresh rate, uses update. update is a new media feature that allows us to “query the ability of the output device to modify the appearance of content once it has been rendered.”

@media screen and   (prefers-reduced-motion: reduce),    (update: slow) {   * {     animation-duration: 0.001ms !important;     transition-duration: 0.001ms !important;   } }

This code forces all animation that utilizes a declaration of animation-duration or transition-duration to conclude at a rate that is imperceptible to the human eye. It will work when a person has requested a reduced motion experience, or the device has a screen with a slow refresh rate, say e-ink or a cheap smartphone.

Retaining the animation and transition duration also ensures that any functionality that is tied to CSS-based animation will activate successfully (unlike using a declaration of animation: none), while still preventing a disability condition trigger or creating rendering lag.

This declaration is authored with the intent of introducing some intentional friction into our reset styles. Granted, it’s not a perfect solution, but it does drive at a few things:

  1. Increasing the chances of developers becoming aware of the two media features, by way of making them present in the cascade of every inspected element.
  2. Providing a moment to consider why and how animation will be introduced into a website or web app, and what the experience should be like for those who can’t or don’t want to experience it.
  3. Encouraging developers who are less familiar with CSS to think of the cascade in terms of components and nudge them towards making more easily maintainable stylesheets.

Animation isn’t unnecessary

In addition to vestibular disorders and photosensitive conditions, there’s another important aspect of accessibility we must consider: cognitive disabilities.

Cognitive disabilities

As a concern, the category is wide and often difficult to quantify, but no less important than any other accessibility discipline. It is also far more prevalent. To expand on this some, the World Health Organization reports an estimated 300 million people worldwide are affected by depression, a temporary or permanent, environmental and/or biological condition that can significantly impair your ability to interact with your environment. This includes interfering with your ability to understand the world around you.

Animation can be a great tool to help combat some forms of cognitive disability by using it to break down complicated concepts, or communicate the relationship between seemingly disparate objects. Val Head’s article on A List Apart highlights some other very well-researched benefits, including helping to increase problem-solving ability, recall, and skill acquisition, as well as reducing cognitive load and your susceptibility to change blindness.

Reduce isn’t necessarily remove

We may not need to throw the baby out with the bathwater when it comes to using animation. Remember, it’s prefers-reduced-motion, not prefers-no-motion.

If we embrace the cascade, we can work with the animation reset code described earlier on a per-component basis. If the meaning of a component is diminished by removing its animation altogether, we could slow down and simplify the component’s animation to the point where the concept can be communicated without potentially being an accessibility trigger.

If you’re feeling clever, you might even be able to use CSS Custom Properties to help achieve this in an efficient way. If you’re feeling extra clever, you could also use these Custom Properties for a site-wide animation preferences widget.

In the following code sample, we’re defining default properties for our animation and transition durations, then modifying them based on the context they’re declared in:

/* Set default durations */ :root {   --animation-duration: 250ms;    --transition-duration: 250ms;  }  /* Contextually shorten duration length */ @media screen and (prefers-reduced-motion: reduce), (update: slow) {   :root {     --animation-duration: 0.001ms !important;      --transition-duration: 0.001ms !important;   } }  @media screen and (prefers-reduced-motion: reduce), (update: slow) {   /* Remove duration for all unknown animation when a user requests a reduced animation experience */   * {     animation-duration: var(--animation-duration);     transition-duration: var(--animation-duration);   } }  /* Update the duration when animation is critical to understanding and the device can support it */ @media screen and (prefers-reduced-motion: reduce), (update: fast) {   .c-educational-concept {     /* Set a new animation duration scoped to this component */     --animation-duration: 6000ms !important;      ...     animation-name: educational-concept;     /* Use the scoped animation duration */     animation-duration: var(--animation-duration);    } }

However, trying to test the effectiveness of this slowed-down animation puts us in a bit of a pickle: there’s no real magic number we can write a test against.

We need to have a wide representation of people who are susceptible to animation-based disability triggers to sign off on it being safe, which unfortunately involves subjecting them to something that may potentially not be. That’s a huge ask.

A better approach is to ask about what kinds of animation have been triggers for them in the past, then see if what they describe matches what we’ve made. This approach also puts the onus on yourself, and not the person with a disability, to do the work to provide accommodation.

If you’re having trouble finding people, ask your friends, family, and coworkers — I’m sure there’s more people out there than you think. And if you need a good starting point for creating safer animation, I once again urge you to read Val’s article on A List Apart.


There’s a lot to unpack here, and I’m not the most qualified person to talk about it. Here’s what my friend Shell Little, an Accessibility Specialist at Wells Fargo DS4B, has to say about it:

Web animation as it relates to Neurodivergence (ND) can be a fantastic tool to guide users to solidify meaning and push understanding. The big issue is the same animation that can assist one group of ND users can create a barrier for another. As mentioned by Eric, Neurodivergence is a massive group of people with a vast range of abilities and covers a wide variety of cognitive disabilities including but not limited to ADHD, autism, dyslexia, epilepsy, dyscalculia, obsessive-compulsive disorder, dyspraxia, and Tourette syndrome.

When speaking about motion on the web it’s important we think specifically about attention-related disabilities, autism, and sensory processing disorders that are also closely linked to both. These groups of people, who coincidentally includes me, are especially sensitive to motion as it relates to understanding information and interacting with the web as a whole. Animations can easily overwhelm, distract, and frustrate users who are sensitive to motion and from personal experience, it can even do all three at once.

Because so many people are affected by motion and animation on the web the W3C’s WCAG have a criterion named Pause, Stop, Hide that is specifically written to guide content creators on how to best create accessible animations. My main issues with this guideline are, it only applies to animations that last longer than 5 seconds and motion that is deemed essential is exempt from the standard. That means a ton of animations that can create barriers such as distraction, dizziness, and even harm are out there in the wild.

It makes sense, as Eric mentioned, that we can’t get rid of all animation. Techniques such as spinners let users know the page is still working on the task it was given, and micro-interactions help show progression. But depending on someone’s brain, the things that are helpful at lunch can be a barrier later that night. Someone’s preferences and needs shift throughout their day, and that’s the beauty of prefers-reduced-motion. It has the potential to be what fills the gaps left by Pause, Stop, Hide and allow users to decide when they do or do not want to have motion. That right there is priceless to someone like me.

As someone with an attention-related disability, an interaction I have found to be exceedingly frustrating is autoplay. Many media sharing sites have auto-playing content such as videos, gifs, and ads but because they can be paused, they pass the WCAG standard. That doesn’t mean they aren’t a huge barrier for me as I can’t read any text around them when they are playing. This causes me to have to pause every single moving item I run into. This not only significantly slows me down, and eats away at my limited spoons, but it also derails my task flow and train of thought. Now, it is true some sites — such as Twitter and LinkedIn — have settings to turn autoplay off, but this isn’t true for all sites. This would be a perfect place for prefers-reduced-motion.

In a world where I would be able to determine when and if I want videos to start playing at me, I would be able to get more done with less cognitive strain. prefers-reduced-motion is freedom for me and the millions of people whose brains work like mine. In sum, the absolute best thing we can do for our users who are sensitive to motion is to put a system in place that empowers them to decide when and where animation should be displayed to them. Let the user decide because they will always know their access needs better than we do.

Thanks, Shell!

I don’t hate fun, I just don’t want to hurt people

On my own time, I’m fortunate enough to be able to enjoy animation. I appreciate the large amounts of time and attention involved with making something come alive on the screen, and I’ve definitely put my fair share of time ooh-ing and aah-ing over other people’s amazing work in CodePen. I’ve also watched enough DC Animated Universe to be able to instantly recognize Kevin Conroy’s voice — if you’re looking for even deeper nerd cred, Masaaki Yuasa is a seriously underrated animator.

However, I try to not overly rely on animation as a web professional. There’s a number of factors as to why:

  1. First is simply pushing on awareness of the concerns outlined earlier, as many are unaware they exist. Animation has such a crowd-pleasing gee-whiz factor to it that it’s often quickly accepted into a product without a second thought.
  2. Second is mitigating risk. Not adhering to the Web Content Accessibility Guidelines (WCAG) — including provisions for animation — means your inaccessible website or web app becomes a legal liability. There is now legal precedent for the websites and web apps of private companies being sued, so it’s a powerful metric to weigh your choices against.
  3. Third is user experience. With that gee-whiz factor, people tend to forget that being forced to repeatedly view that super-slick animation over and over again will eventually become a tedious chore. There’s a reason why we no longer make 90s-style loading screens (content warning: high-contrast strobing and flickering, Flash, mimes). If you need a more contemporary example, consider why Netflix lets us skip TV show intros.
  4. Fourth is understanding the lay of the land. While prefers-reduced-motion is getting more support, the majority of it is on desktop browsers, and not mobile. We’re not exactly a desktop-first world anymore, especially if you’re in an underserved community or emerging market. A mobile form factor also may exacerbate vestibular issues. Moving around while using your device means you may lose a fixed reference point, unlike sitting at a desk and staring at a monitor — this kind of trigger is similar to why some of us can get seasick.
  5. The fifth factor is a bit of a subset of the fourth. Animation eats device data and battery, and it’s important to remember that it’s the world wide web, not the wealthy Western web. The person using your service may not have consistent and reliable access to income or power, so you want to get to know your audience before spending their money for them.

The ask

Not everyone who could benefit from prefers-reduced-motion cares about accessibility-related content, so I’d love to see the media query start showing up in the code of more popular sites. The only real way to do this is to spread awareness. Not only of the media query, but more importantly, understanding the nuance involved with using animation responsibly.

CSS-Tricks is a popular website for the frontend industry, and I’m going to take advantage of that. If you feel comfortable sharing, what I would love is to describe what kinds of animation have been problematic for you, in either the comments or on Twitter.

The idea here is we can help build a reference of what kinds of things to be on the lookout for animation-wise. Hopefully, with time and a little luck, we can all help make the web better for everyone.

Thanks to Scott O’Hara, Zach Leatherman, Shell Little, and Geoff Graham for reviewing this article.

The post Revisiting prefers-reduced-motion, the reduced motion media query appeared first on CSS-Tricks.


, , , , ,