The State of CSS survey recently opened up. Last year, the survey confirmed everyone’s assumptions that TailwindCSS is super popular and CSS variables are mainstream. It also codified what many of us want from CSS, from Container Queries to a parent selector. (Spoiler alert, we now have both of ’em.)
While I wouldn’t say the results have been super surprising each year, this time I’m excited to start seeing more historical trends reveal themselves. The survey has been running since 2019, so that’s going to be four years (ancient in front-end years!) of data to see if certain frameworks came and went, specific features are gaining momentum, what general learning practices are out there, and just plain more context. It takes time for stuff to build up like this, so kudos to Sacha Greif for keeping this thing going.
And speaking of the team behind the survey, Lea Verou is new to the bunch and lead this year’s edition. Lea made some nice additions, including more open-ended comments, questions about browser inconsistencies, and a question that compares the amount of time you write CSS versus JavaScript.
Browsers actually use this stuff to help prioritize what features to work on — so definitely add your voice to the mix! The polls close on October 20.
CSS-only Wolfenstein is a little project that I made a few weeks ago. It was an experiment with CSS 3D transformations and animations.
Inspired by the FPS demo and another Wolfenstein CodePen, I decided to build my own version. It is loosely based on Episode 1 – Floor 9 of the original Wolfenstein 3D game.
Editor: This game intentionally requires some quick reaction to avoid a Game Over screen.
Here is a playthrough video:
In a nutshell, my project is nothing but a carefully scripted long CSS animation. Plus a few instances of the checkbox hack.
:checked ~ div { animation-name: spin; }
The environment consists of 3D grid faces and the animations are mostly plain 3D translations and rotations. Nothing really fancy.
However, two problems were particularly tricky to solve:
Play the “weapon firing” animation whenever the player clicks on an enemy.
When the fast-moving boss got the last hit, enter a dramatic slow motion.
At a technical-level, this meant:
Replay an animation when the next checkbox is checked.
Slow down an animation, when a checkbox is checked.
In fact, neither was properly solved in my project! I either ended up using workarounds or just gave up.
On the other hand, after some digging, eventually I found the key to both problems: altering the properties of running CSS animations. In this article, we will explore further on this topic:
Lots of interactive examples.
Dissections: how does each example work (or not work)?
Behind-the-scene: how do browsers handle animation states?
My first intuition was “just add another checkbox”, which does not work:
Each checkbox works individually, but not both together. If one checkbox is already checked, the other no longer works.
Here’s how it works (or “does not work”):
The animation-name of <div> is none by default.
The user clicks on one checkbox, animation-name becomes spin, and the animation starts from the beginning.
After a while, the user clicks on the other checkbox. A new CSS rule takes effect, but animation-name is stillspin, which means no animation is added nor removed. The animation simply continues playing as if nothing happened.
The second example: “cloning the animation”
One working approach is to clone the animation:
#spin1:checked ~ div { animation-name: spin1; } #spin2:checked ~ div { animation-name: spin2; }
Here’s how it works:
animation-name is none initially.
The user clicks on “Spin!”, animation-name becomes spin1. The animation spin1 is started from the beginning because it was just added.
The user clicks on “Spin again!”, animation-name becomes spin2. The animation spin2 is started from the beginning because it was just added.
Note that in Step #3, spin1 is removed because of the order of the CSS rules. It won’t work if “Spin again!” is checked first.
The third example: “appending the same animation”
Another working approach is to “append the same animation”:
#spin1:checked ~ div { animation-name: spin; } #spin2:checked ~ div { animation-name: spin, spin; }
This is similar to the previous example. You can actually understand the behavior this way:
#spin1:checked ~ div { animation-name: spin1; } #spin2:checked ~ div { animation-name: spin2, spin1; }
Note that when “Spin again!” is checked, the old running animation becomes the second animation in the new list, which could be unintuitive. A direct consequence is: the trick won’t work if animation-fill-mode is forwards. Here’s a demo:
If you wonder why this is the case, here are some clues:
animation-fill-mode is none by default, which means “The animation has no effect at all if not playing”.
animation-fill-mode: forwards; means “After the animation finishes playing, it must stay at the last keyframe forever”.
spin1’s decision always override spin2’s because spin1 appears later in the list.
Suppose the user clicks on “Spin!”, waits for a full spin, then clicks on “Spin again!”. At this moment. spin1 is already finished, and spin2 just starts.
Discussion
Rule of thumb: you cannot “restart” an existing CSS animation. Instead, you want to add and play a new animation. This may be confirmed by the W3C spec:
Once an animation has started it continues until it ends or the animation-name is removed.
Now comparing the last two examples, I think in practice, “cloning animations” should often work better, especially when CSS preprocessor is available.
Problem 2: Slow Motion
One might think that slowing an animation is just a matter of setting a longer animation-duration:
div { animation-duration: 0.5s; } #slowmo:checked ~ div { animation-duration: 1.5s; }
Indeed, this works:
… or does it?
With a few tweaks, it should be easier to see the issue.
Yes, the animation is slowed down. And no, it does not look good. The dog (almost) always “jumps” when you toggle the checkbox. Furthermore, the dog seems to jump to a random position rather than the initial one. How come?
It would be easier to understand it if we introduced two “shadow elements”:
Both shadow elements are running the same animations with different animation-duration. And they are not affected by the checkbox.
When you toggle the checkbox, the element just immediately switches between the states of two shadow elements.
It does slow down when you click on “Slowmo!”. But if you wait for a full circle, you will see a “jump”. Actually, it always jumps to the position when “Slowmo!” is clicked on.
The reason is we don’t have a from keyframe defined – and we shouldn’t. When the user clicks on “Slowmo!”, spin1 is paused at some position, and spin2 starts at exactly the same position. We simply cannot predict that position beforehand … or can we?
A Working Solution
We can! By using a custom property, we can capture the angle in the first animation, then pass it to the second animation:
div { transform: rotate(var(--angle1)); animation-name: spin1; animation-duration: 2s; } #slowmo:checked ~ div { transform: rotate(var(--angle2)); animation-name: spin1, spin2; animation-duration: 2s, 5s; animation-play-state: paused, running; } @keyframes spin1 { to { --angle1: 360deg; } } @keyframes spin2 { from { --angle2: var(--angle1); } to { --angle2: calc(var(--angle1) + 360deg); } }
There is a caveat to the previous solution: “exiting slowmo” does not work well.
Here is a better solution:
In this version, slow motion can be entered or exited seamlessly. No experimental feature is used either. So is it the perfect solution? Yes and no.
This solution works like “shifting” “gears”:
Gears: there are two <div>s. One is the parent of the other. Both have the spin animation but with different animation-duration. The final state of the element is the accumulation of both animations.
Shifting: At the beginning, only one <div> has its animation running. The other is paused. When the checkbox is toggled, both animations swap their states.
While I really like the result, there is one problem: it is a nice exploit of the spin animation, which does not work for other types of animations in general.
A Practical Solution (with JS)
For general animations, it is possible to achieve the slow motion function with a bit of JavaScript:
A quick explanation:
A custom property is used to track the animation progress.
The animation is “restarted” when the checkbox is toggled.
The JS code computes the correct animation-delay to ensure a seamless transition. I recommend this article if you are not familiar with negative values of animation-delay.
You can view this solution as a hybrid of “restarting animation” and the “gear-shifting” approach.
Here it is important to track the animation progress correctly. Workarounds are possible if @property is not available. As an example, this version uses z-index to track the progress:
Side-note: originally, I also tried to create a CSS-only version but did not succeed. While not 100% sure, I think it is because animation-delay is not animatable.
Here is a version with minimal JavaScript. Only “entering slowmo” works.
Please let me know if you manage to create a working CSS-only version!
Slow-mo Any Animation (with JS)
Lastly, I’d like to share a solution that works for (almost) any animation, even with multiple complicated @keyframes:
Basically, you need to add an animation progress tracker, then carefully compute animation-delay for the new animation. However, sometimes it could be tricky (but possible) to get the correct values.
For example:
animation-timing-function is not linear.
animation-direction is not normal.
multiple values in animation-name with different animation-duration’s and animation-delay’s.
As it turned out, building and animating 3D objects with CSS is not much different from doing it with Blender, just with a bit different flavor.
Conclusion
In this article we examined the behavior of CSS animations when an animate-* property is altered. Especially we worked out solutions for “replaying an animation” and “animation slow-mo”.
I hope you find this article interesting. Please let me know your thoughts!
You gotta appreciate the tenacity of Surma. He’s been advocating for Web Workers as a path forward to better-feeling websites for a lot of years now. He’s at it again making sure we all understand the landscape:
[…] regardless of where you look, multithreading is used everywhere. iOS empowers developers to easily parallelize code using Grand Central Dispatch, Android does this via their new, unified task scheduler WorkManager and game engines like Unity have job systems. The reason for any of these platforms to not only support multithreading, but making it as easy as possible is always the same: Ensure your app feels great.
So pretty much every platform has its own version of multi-threading, including the web. It’s just that on the web we have to sort of “fight” against the single-threaded nature of JavaScript by using Web Workers (which are “universally supported” if you’re wondering about that). The question is: use them how and for what? For the latter, Surma shows off an example of a game where “the entire app state and game logic is running in a worker.” For the former, the helper library comlink looks like a big reduction in toil.
Personally, I wish popular tooling would just kinda… do it. I don’t know what that really looks like, but it kinda feels like developer outreach isn’t really moving the needle on this. What if popular tooling like Apollo — which is in charge of a lot of “app state” — were to magically handle all of that off the main thread. Does that make sense? Is it possible?
There is an increasing number of “custom” features on the web platform. We have custom properties (--my-property), custom elements (<my-element>), and custom events (new CustomEvent('myEvent')). At one point, we might even get custom media queries (@media (--my-media)).
But that’s not all! You might have missed it because it wasn’t mentioned in Google’s “New in Chrome 90” article (to be fair, declarative shadow DOM stole the show in this release), but Chrome just added support for yet another “custom” feature: custom state pseudo-classes (:--my-state).
Built-in states
Before talking about custom states, let’s take a quick look at the built-in states that are defined for built-in HTML elements. The CSS Selectors module and the “Pseudo-classes” section of the HTML Standard specify a number of pseudo-classes that can be used to match elements in different states. The following pseudo-classes are all widely supported in today’s browsers:
User action
:hover
the mouse cursor hovers over the element
:active
the element is being activated by the user
:focus
the element has the focus
:focus-within
the element has or contains the focus
Location
:visited
the link has been visited by the user
:target
the element is targeted by the page URL’s fragment
the input element has been autofilled by the browser
Other
:defined
the custom element has been registered
Note: For brevity, some pseudo-classes have been omitted, and some descriptions don’t mention every possible use-case.
Custom states
Like built-in elements, custom elements can have different states. A web page that uses a custom element may want to style these states. The custom element could expose its states via CSS classes (class attribute) on its host element, but that’s considered an anti-pattern.
Chrome now supports an API for adding internal states to custom elements. These custom states are exposed to the outer page via custom state pseudo-classes. For example, a page that uses a <live-score> element can declare styles for that element’s custom --loading state.
live-score { /* default styles for this element */ } live-score:--loading { /* styles for when new content is loading */ }
Let’s add a --checked state to a <labeled-checkbox> element
The Custom State Pseudo Class specification contains a complete code example, which I will use to explain the API. The JavaScript portion of this feature is located in the custom element‘s class definition. In the constructor, an “element internals” object is created for the custom element. Then, custom states can be set and unset on the internal states object.
Note that the ElementInternals API ensures that the custom states are read-only to the outside. In other words, the outer page cannot modify the custom element’s internal states.
class LabeledCheckbox extends HTMLElement { constructor() { super(); // 1. instantiate the element’s “internals” this._internals = this.attachInternals(); // (other code) } // 2. toggle a custom state set checked(flag) { if (flag) { this._internals.states.add("--checked"); } else { this._internals.states.delete("--checked"); } } // (other code) }
The web page can now style the custom element’s internal states via custom pseudo-classes of the same name. In our example, the --checked state is exposed via the :--checked pseudo-class.
labeled-checkbox { /* styles for the default state */ } labeled-checkbox:--checked { /* styles for the --checked state */ }
It looks like this feature has good support. It sounds that it may be hard for web developers to benefit from it as long as it’s not widely shipped, but hopefully Firefox and Safari will follow and implement it too. Someone has to implement it first, and given that there are no foreseeable backward incompatible changes, it sounds safe to go first.
We now have to wait for the implementations in Firefox and Safari. The browser bugs have been filed (Mozilla #1588763 and WebKit #215911) but have not received much attention yet.
React is actually a bit of an outlier with state management. While it has first-class tools like useState and Context, you’re more own your own for reactive global state. Here’s David Ceddia with “React State Management Libraries and How to Choose” which does a good job of talking about the options. I say “outlier” because every other major JavaScript framework has its own blessed global state implementations.
To me, the concept of state is vital to front-end development. It’s like components in that way. It’s just shaking out that it’s a smart way to work on digital products. State is our own abstraction of what is happening on a site. It can be whether a sidebar is open or closed, a list of comments data, the details of logged-in users, or anything else that we need to draw and make functional UI.
That’s why it still feels surprising to me that native web components didn’t attempt to tackle the idea of state at all. I’m not informed enough to know why that is, but as an observer, I can see that developers are clamoring to find the best ways to make state work in and across web components. Most recently, I came across @vue/lit by Evan You. That is a microframework for web components that solves templating and re-rendering by using lit-html, and then incorporating reactive state with Vue’s reactivity. Looks pretty cool to me.
Evan’s idea takes the the combined weight of libraries in use to ~6kb. So how low can we go here? Krasimir Tsonev wrote “Using JavaScript module system for state management” where they use no libraries at all (arguably creating a small one of their own along the way). A state manager can just be a module we import and use that is essentially an Object with values, mutation functions, and listeners. That takes the overhead of state mangement down to just about nothing, at the cost of giving up the efficient re-rendering, better templating, and lifecycle stuff you’d get by using more robust libraries.
Speaking of not using any libraries at all, here’s Leo Bauza with “How does Viget JavaScript?” where they go into the vanilla pattern they use to add functionality on top of an HTML foundation. It looks like all functionality is applied via data-* attributes, and each data attribute has its own JavaScript module (Class) that handles that specific bit of functionality. It doesn’t look like the deal with global state here, but they do handle state rather manually within the modules.
I find all this stuff fascinating. In my own work, I bet I’m rather typical. If it’s a small baby thing, I might be up for a roll-my-own pattern. If it’s a medium-sized thing but sorta low-impact, I’d probably reach for the new-and-fancy — and maybe even experimental — takes. But the second I’m doing something big and high-impact, I find way more comfort in picking from the biggest players, even if that sometimes means heavier libraries. 😬
Just shy of 24,000 folks participated in this year’s survey… almost exactly 2,000 more than 2019.
I love charts like this:
Notice how quickly some technologies take off then start to gain negative opinions, even as the rate of adoption increases.
What I like about this particular survey (and the State of CSS) is how the data is readily available to export in addition to all the great and telling charts. That opens up the possibility of creating your own reports and gleaning your own insights. But here’s what I’ve found interesting in the short time I’ve spent looking at it:
React’s facing negative opinions. It’s not so much that everybody’s fleeing from it, but the “shiny” factor may be waning (coming in at 88% satisfaction versus a 93% peak in 2017). Is that because it suffers from the same frustration that devs expressed with a lack of documentation in other surveys? I don’t know, but the fact that we see both growth and a sway toward negative opinions is interesting enough that I’ll want to see where it goes in 2021.
Awwwww, Gulp, what happened?! Wow, what a change in perception. Its usage has dipped a bit, but the impression of it is now solidly in “negative opinions” territory. I still use it personally on a number of projects and it does exactly what I need, but I get that there are better build processes these days that don’t require writing a bunch of pipes and whatnot.
Hello, Svelte. It’s the most fourth most used framework (15%) but enjoys the highest level of satisfaction (89%). I’m already interested in giving it a go, but this makes me want to dive into it even more — which is consistent with the fact that it also garners the most interest of all frameworks (68%).
Javascript is sorta overused and sorta overly complex. Well, according to the polls. It’s just so interesting that the distribution between the opinions is almost perfectly even. At the same time, the vast majority of folks (80.6%) believe JavaScript is heading in the right direction.
Here’s a fun idea from James Stanley: a CSS file (that presumably updates daily) containing CSS custom properties for “seasonal” colors (e.g. spring is greens, fall is oranges). You’d then use the values to theme your site, knowing that those colors change slightly from day to day.
I think it would be more fun if the CSS file provided was just the custom properties and not the opinionated other styles (like what sets the body background and such). That way you could implement the colors any way you choose without any side effects.
This makes me think that a CDN-hosted CSS file like this could have other useful stuff, like today’s date for usage in pseudo content, or other special time-sensitive stuff. Maybe the phase of the moon? Sports scores?! Soup of the day?!
/* <div class="soup">The soup of the day is: </div> */ .soup::after { content: var(--soupOfTheDay); /* lol kinda */ }
It’s almost like a data API that is tremendously easy to use. Pseudo content is even accessible content these days — but you can’t select the text of pseudo-elements, so don’t read this as an actual endorsement of using CSS as a content API.
Will Boyd just blogged about what is possible to put in a custom property. They are tremendously flexible. Just about anything is a valid custom property value and then the usage tends to behave just how you think it will.
body { /* totally fine */ --rgba: rgba(255, 0, 0, 0.1); background: var(--rgba); /* totally fine */ --rgba: 255, 0, 0, 0.1; background: rgba(var(--rgba)); /* totally fine */ --rgb: 255 0 0; --a: 0.1; background: rgb(var(--rgb) / var(--a)); } body::after { /* totally fine */ --song: "I need quotes to be pseudo content \A and can't have line breaks without this weird hack \A but still fairly permissive (💧💧💧) "; content: var(--song); white-space: pre; }
Bram Van Damme latched onto that flexiblity while covering Will’s article:
Add a couple of scoops of complexity and you get The Raven (media queries with custom properties).
Will calls them “CSS variables” which is super common and understandable. You’ll read (and I have written) sentences often that are like “CSS variables (a.k.a CSS Custom Properties)” or “CSS Custom Properties (a.k.a CSS Variables.” Šime Vidas recently noted there is a rather correct way to refer to these things: --this-part is the custom property and var(--this-part) is the variable, which comes right from usage in the spec.
And that reminds me of this Vue proposal. I’m not sure if it went anywhere, but the state of a component would automatically be exposed as CSS custom properties.
By virtue of having color as part of the state of this component, then --color is available as state to the CSS of this component. I think that’s a great idea.
What if every time you used useState in React, CSS custom properties were put on the :root and were updated automatically. For example, if you did this:
Because the state automatically mapped itself to a custom property. Someone should make a useStateWithCustomProperties hook or something to do that. #freeidea
Libraries like React and Vue are for building UI. I think it makes a lot of sense that the state that they manage is automatically exposed to CSS.
And speaking of state that CSS should know about, I’ve seen quite a few demos that do fun stuff by mapping over things, like the current mouse position or scroll position, over to CSS. I don’t think it’s entirely unreasonable to ask for that data to be natively exposed to CSS. We already have the concept of environment variables, like env(safe-area-inset-top), and I could see that being used to expose page state, like env(page-scroll-percentage) or env(mouseY).
The HTTP Archive looked at more than 7 million websites and compiled their annual report detailing how the sites were built. And there’s an enormous wealth of information about how the web changed in 2020. In fact, this report is more like an enormous book and it’s entirely fabulous. The data comes from making queries to the HTTP Archive and is broken down into various sections, such as Performance, Security, and the languages themselves, including how folks wrote HTML or CSS.
Here’s what the report has to say about the CSS they scanned:
While JavaScript far surpasses CSS in its share of page weight, CSS has certainly grown in size over the years, with the median desktop page loading 62 KB of CSS code, and 1 in 10 pages loading more than 240 KB of CSS code. Mobile pages do use slightly less CSS code across all percentiles, but only by 4 to 7 KB. While this is definitely greater than previous years, it doesn’t come close to JavaScript’s whopping median of 444 KB and top 10% of 1.2 MB
Gasp! And here’s a shocking bit of info that shortly follows:
[…] only about 7% of pages concentrate all their CSS code in one remote stylesheet, as we are often taught to do. In fact, the median page contains 3 <style> elements and 6 (!) remote stylesheets, with 10% of them carrying over 14 <style> elements and over 20 remote CSS files! While this is suboptimal on desktop, it really kills performance on mobile, where round-trip latency is more important than raw download speed.
I sort of want to quote the whole section about CSS specifically because there’s a lot of interesting facts that show how we, as a community, have a lot of work to do to improve performance and spread the good word about CSS optimization.
State machines are typically expressed on the web in JavaScript and often through the popular XState library. But the concept of a state machine is adaptable to just about any language, including, amazingly, HTML and CSS. In this article, we’re going to do exactly that. I recently built a website that included a “no client JavaScript” constraint and I needed one particular unique interactive feature.
The key to all this is using <form> and <input type="radio"> elements to hold a state. That state is toggled or reset with another radio <input> or reset <button> that can be anywhere on the page because it is connected to the same <form> tag. I call this combination a radio reset controller, and it is explained in more detail at the end of the article. You can add more complex state with additional form/input pairs.
It’s a little bit like the Checkbox Hack in that, ultimately, the :checked selector in CSS will be doing the UI work, but this is logically more advanced. I end up using a templating language (Nunjucks) in this article to keep it manageable and configurable.
Traffic light state machine
Any state machine explanation must include the obligatory traffic light example. Below is a working traffic light that uses a state machine in HTML and CSS. Clicking “Next” advances the state. The code in this Pen is post processed from the state machine template to fit in a Pen. We’ll get into the code in a more readable fashion later on.
Hiding/Showing table information
Traffic lights aren’t the most practical every-day UI. How about a <table> instead?
There are two states (A and B) that are changed from two different places in the design that affect changes all over the UI. This is possible because the empty <form> elements and <input> elements that hold state are at the very top of the markup and thus their state can be deduced with general sibling selectors and the rest of the UI can be reached with descendent selectors. There is a loose coupling of UI and markup here, meaning we can change the state of almost anything on the page from anywhere on the page.
General four-state component
Diagram of a generic four-state finite state machine
The goal is a general purpose component to control the desired state of the page. “Page state” here refers to the desired state of the page and “machine state” refers to the internal state of the controller itself. The diagram above shows this generic state machine with four states(A, B, C and D). The full controller state machine for this is shown below. It is built using three of the radio reset controller bits. Adding three of these together forms a state machine that has eight internal machine states (three independent radio buttons that are either on or off).
Diagram of the controller’s internal states
The “machine states” are written as a combination of the three radio buttons (i.e. M001 or M101). To transition from the initial M111 to M011, the radio button for that bit is unset by clicking on another radio <input> in the same group. To transition back, the reset <button> for the <form> attached to that bit is clicked which restores the default checked state. Although this machine has eight total states, only certain transitions are possible. For instance, there is no way to go directly from M111 to M100 because it requires two bits to be flipped. But if we fold these eight states into four states so that each page state shares two machine states (i.e. A shares states M111 and M000) then there is a single transition from any page state to any other page state.
Reusable four-state component
For reusability, the component is built with Nunjucks template macros. This allows it to be dropped into any page to add a state machine with the desired valid states and transitions. There are four required sub-components:
Controller
CSS logic
Transition controls
State classes
Controller
The controller is built with three empty form tags and three radio buttons. Each of the radio buttons checked attribute is checked by default. Each button is connected to one of the forms and they are independent of each other with their own radio group name. These inputs are hidden with display: none because they are are not directly changed or seen. The states of these three inputs comprise the machine state and this controller is placed at the top of the page.
The logic that connects the controller above to the state of the page is written in CSS. The Checkbox Hack uses a similar technique to control sibling or descendant elements with a checkbox. The difference here is that the button controlling the state is not tightly coupled to the element it is selecting. The logic below selects based on the “checked” state of each of the three controller radio buttons and any descendant element with class .M000. This state machine hides any element with the .M000 class by setting display: none !important. The !important isn’t a vital part of the logic here and could be removed; it just prioritizes the hiding from being overridden by other CSS.
{%macro FSM4S_css()%} <style> /* Hide M000 (A1) */ input[data-rrc="Bx00"]:not(:checked)~input[data-rrc="B0x0"]:not(:checked)~input[data-rrc="B00x"]:not(:checked)~* .M000 { display: none !important; } /* one section for each of 8 Machine States */ </style> {%endmacro%}
Transition control
Changing the state of the page requires a click or keystroke from the user. To change a single bit of the machine state, the user clicks on a radio button that is connected to the same form and radio group of one of the bits in the controller. To reset it, the user clicks on a reset button for the form connected to that same radio button. The radio button or the reset button is only shown depending on which state they are in. A transition macro for any valid transition is added to the HTML. There can be multiple transitions placed anywhere on the page. All transitions for states currently inactive will be hidden.
The three components above are sufficient. Any element that depends on state should have the classes applied to hide it during other states. This gets messy. The following macros are used to simplify that process. If a given element should be shown only in state A, the {{showA()}} macro adds the states to hide.
The markup for the traffic light example is shown below. The template macros are imported in the first line of the file. The CSS logic is added to the head and the controller is at the top of the body. The state classes are on each of the lights of the .traffic-light element. The lit signal has a {{showA()}} macro while the “off” version of signal has the machine states for the .M000 and .M111 classes to hide it in the A state. The state transition button is at the bottom of the page.
The state machine component here includes up to four states which is sufficient for many use cases, especially since it’s possible to use multiple independent state machines on one page.
That said, this technique can be used to build a state machine with more than four states. The table below shows how many page states can be built by adding additional bits. Notice that an even number of bits does not collapse efficiently, which is why three and four bits are both limited to four page states.
Bits (rrcs)
Machine states
Page states
1
2
2
2
4
2
3
8
4
4
16
4
5
32
6
Radio reset controller details
The trick to being able to show, hide, or control an HTML element anywhere on the page without JavaScript is what I call a radio reset controller. With three tags and one line of CSS, the controlling button and controlled element can be placed anywhere after this controller. The controlled side uses a hidden radio button that is checked by default. That radio button is connected to an empty <form> element by an ID. That form has a type="reset" button and another radio input that together make up the controller.
This shows a minimal implementation. The hidden radio button and the div it controls need to be siblings, but that input is hidden and never needs to be directly interacted with by the user. It is set by a default checked value, cleared by the other radio button, and reset by the form reset button.
Only two line of CSS are required to make this work. The :checked pseudo selector connects the hidden input to the sibling it is controlling. It adds the radio input and reset button that can be styled as a single toggle, which is shown in the following Pen:
Accessibility… should you do this?
This pattern works, but I am not suggesting it should be used everywhere for everything. In most cases, JavaScript is the right way to add interactivity to the web. I realize that posting this might get some heat from accessibility and semantic markup experts. I am not an accessibility expert, and implementing this pattern may create problems. Or it may not. A properly labelled button that does something to the page controlled by otherwise-hidden inputs might work out fine. Like anything else in accessibility land: testing is required.
Also, I have not seen anyone else write about how to do this and I think the knowledge is useful — even if it is only appropriate in rare or edge-case situations.
Chris and Dave disussed the 2019 State of CSS results with co-creator Sacha Greif on ShopTalk Show and dive into many, many more of the results and what they tell us about our industry.
But hey, it’s been [checking my calendar] slightly over a year since that survey and it’s back with a 2020 edition. If you can take it, please do — the more years we have on record, the more interesting trends we can find. Plus, we’ve got a whole bunch of new features and technologies to evaluate, like subgrid, user preference media queries, logical properties, line-clamp(), min(), max(), yada, yada, the list goes on!