0

Some use cases for revert-layer

 7 months ago
source link: https://www.mayank.co/blog/revert-layer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Some use cases for revert-layer

Some use cases for revert-layer

Published on January 30, 2024

Did you know about the revert-layer keyword? It belongs in the same family as other CSS-wide global values, namely inherit, initial, unset, revert; all of which can be used on any (and all) properties.

revert-layer is the only part of cascade layers that cannot be easily polyfilled. This may have deterred some developers from using it in the past. Don’t believe caniuse though; I’ve found that it has worked fine in the three major browser engines ever since their impressive coordinated rollout of cascade layers back in February 2022.

The nuclear reset

If you’ve ever tried using all: revert, then you’re probably aware of what a disappointment it is. On paper it gives you a clean slate, but in practice it does too much. It gets rid of (desirable!) styles that come with draggable, contenteditable, svg attributes, etc. (I think these are called “presentational hints”? Super weird.)

Anyway, all: revert-layer is like all: revert but actually useful. It can be used as a drop-in replacement for all: revert, even if you are not making use of cascade layers anywhere else. When there is no “previous layer”, revert-layer will revert to the previous origin.

One recurring use I’ve found for this is when I want to isolate a portion of the page (e.g. for demos, previews or third-party components), preventing document styles from leaking in.

my-demo {
all: revert-layer;

In many ways, all: revert-layer offers better encapsulation than shadow DOM, because it will reset everything to the browser defaults, even inherited typographic properties. Use with caution, but confidently!

Do note that if you have too many layers, you will need to revert them all one by one. (I don’t think this is a bad thing; I actually like that it forces you to be more intentional about your layers and sublayers, so you don’t end up with stray top-level layers).

my-demo {
@layer reset {
all: revert-layer;
@layer globals {
all: revert-layer;
@layer components {
all: revert-layer;
@layer pages {
all: revert-layer;

The surgical reset

Building components using cascade layers opens up the possibility of conditionally resetting some properties.

For example, your reset might remove the default list styles, which is fine for 99% cases. But for prose content, you actually want those list styles. You can easily bring them back! revert-layer is particularly great for these “edge case”-y scenarios.

.prose :is(ul, ol) {
@layer reset {
list-style: revert-layer;

Another example: you can revert a layer based on multiple conditions without duplication. You might implement a high-contrast mode on your website, controlled using not just the poorly-supported prefers-contrast query but also a custom toggle. It’s kinda like runtime mixins?

@media not (prefers-contrast: more) {
html:not([data-contrast]) * {
@layer components.highcontrast {
all: revert-layer;
html[data-contrast='low'] * {
@layer components.highcontrast {
all: revert-layer;

The beauty of this technique is that you get to control exactly “how much” of the cascade to revert (by specifying layers and property names) and “when” to revert it (by specifying selectors and queries).

The versioned reset

Let’s say you have a .button class used in a million different places, and now it’s time for a redesign.

One way of going about this is by creating a .button-new class, but then all the markup needs to be updated. This is not always feasible, especially when multiple teams are involved.

Layers provide a natural way of versioning CSS, enabling seamless incremental migrations without causing cascade wars.

You can put all your old styles in a v1 layer. Heck, this layer could even be named legacy if you don’t care about actual versioning. Point is, this layer can be easily reverted for a specific sub-tree.

.new-ui * {
@layer versions.v1 {
all: revert-layer;

Realistically, that selector will need to be adjusted to consider generated content. Its specificity will also need to be artifically inflated (using conflicting id selectors), so that it takes precedence over all other selectors in that layer. It might look hacky, but it works quite well!

.new-ui :not(#a):not(#b) {
&::before,
&::after {
@layer versions.v1 {
all: revert-layer;

The self-reset

You know how using a custom property overrides any previous declarations even if the custom property doesn’t have a value?

@layer base {
width: 1rem;
height: 1rem;
/* These will throw away the base width/height,
even if --size is undefined :( */
width: var(--size);
height: var(--size);
fill: var(--fill);

To get around this, you can specify a fallback value for the custom property (maybe even combined with pseudo-private custom properties), but that usually involves restructuring/rewriting your code. And depending on which layer you use it in, it can still end up incorrectly throwing away valid declarations from lower-priority layers.

Here’s the trick: you can specify revert-layer as the fallback! If the custom property never gets set, it will be automatically reverted, as if it never existed in the first place. Declarations from lower layers will continue to work.

width: var(--size, revert-layer);
height: var(--size, revert-layer);
fill: var(--fill, revert-layer);

CSS is a proper programming language, alright.

The un-!important reset

The role of !important has evolved from a shameful hack to a robust technique for managing priorities. Instead of attempting to poorly explain how the cascade works, I’m going to suggest you read Miriam Suzanne’s excellent guide to cascade layers if you haven’t already.

I’ve started using !important in quite a few places now, because revert-layer makes it so simple to revert them later. It’s like saying “this declaration is no longer important”, which just feels way more intentional.

Consider these three parts of a CSS reset:

  1. Reinforced hidden attribute and safeguards for dialog/popover.

    @layer reset {
    :where([hidden]:not([hidden='until-found'])) {
    display: none !important;
    :where(dialog:not([open]), [popover]:not(:popover-open)) {
    display: none !important;
  2. The visually-hidden ruleset (which should just be built into CSS at this point btw).

    @layer reset.visually-hidden {
    :where(.visually-hidden:not(:focus-within, :active)) {
    clip-path: inset(50%) !important;
    height: 1px !important;
    width: 1px !important;
    overflow: hidden !important;
    position: absolute !important;
    white-space: nowrap !important;
    border: 0 !important;
  3. The universal focus indicator.

    @layer reset {
    :where(:focus-visible) {
    outline: 3px solid CanvasText !important;
    box-shadow: 0 0 0 6px Canvas;

I can safely use !important in all of these (and more!) places. !important prevents the possibility of some higher layer potentially messing up my carefully orchestrated resets and utility classes.

When there are legitimate reasons for reverting these styles, revert-layer avoids any specificity battles that would traditionally result from the use of !important:

  1. If I need more control over the display property, for animation reasons or otherwise.

    .animated-dialog {
    @layer reset {
    display: revert-layer;
    visibility: hidden;
  2. If I need to “unhide” a visually-hidden element when a parent element is hovered or has focus. (Think of a card which might have other focusable elements).

    .my-card:is(:hover, :focus-within) {
    .my-hidden-button {
    @layer reset.visually-hidden {
    all: revert-layer;
  3. If I need a custom focus indicator for some special control.

    .my-component:focus-visible {
    @layer reset {
    outline: revert-layer;
    /* … alternative focus styles, presumably */

Bonus: the super-nuclear reset

I want to give a shoutout to Nathan Knowler’s post about shadow-less style encapsulation, where he describes how you can use all: revert-layer !important to achieve shadowDOM-like encapsulation. Go read it!

@layer components {
.icon-button {
@layer {
color: hotpink;
/* … all scoped styles go in this nested layer */
/* This prevents "outside" styles from bleeding in,
by reverting any styles from subsequent layers */
all: revert-layer !important;

Currently obsessed with Paved In Sorrow

© Mayank


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK