5

CSS Custom Properties Beyond the :root · Matthias Ott – User Experience Designer

 1 year ago
source link: https://matthiasott.com/notes/custom-properties-beyond-the-root
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

CSS Custom Properties Beyond the :root

Manuel asked:

Is there a good reason why we’re defining global custom properties on :root/html and not on body?”

It’s a great question: Everybody just seems to define most of their global custom properties (aka CSS variables) on the :root selector without giving it a second thought – and so am I. But why :root?

The answer is that there is no real reason. It’s just a convention. Defining custom properties on the :root selector might make them look a bit more like global variables, because :root is the equivalent of the root element of your document. In HTML, this is the html element. In SVG, it is the svg element. But besides this potential gain of flexibility – in case you ever wanted to apply the same properties to different types of documents – there is no real technical benefit. You could just as well define all global custom properties on the body element and they would still work exactly the same way.

Sure, you would need to write your JavaScript in a slightly different way, if you wanted to work with your custom properties in JS. Instead of accessing them via the document root element like this:

getComputedStyle(document.documentElement).getPropertyValue("--color")

you would need to retrieve or set their values on the body element:

getComputedStyle(document.body).getPropertyValue("--color")

But this also is just a matter of preference.

Also, the fact that :root has a higher specificity than html might be interesting to know but also doesn’t really make a difference in practice.

Avoiding :root Growth

So yes, there is no real reason why we are all using the :root selector to define our global custom properties. But what’s really interesting to me is that by using this convention, we also seem to have become a bit complacent in the way we use custom properties. Because defining most or all of your custom properties on :root can also become a problem. Kevin Powell has written about this on CSS-Tricks: if you just use this single place for all your custom properties, you might be able to manage all the “settings” of your CSS in one place, but you will also lose a lot of flexibility. When building components, for example, it is important to design and code them in such a way that you can easily change the implementation or add or remove them without affecting the global code around them or even breaking other parts of the system. Globally defined local properties would make that a lot harder. There is just no reason to define local custom properties in :root that actually belong to individual components.

As Lea Verou noted in her brilliant talk CSS Variable Secrets at CSS Day last year, we are also still not leveraging the full potential of custom properties when it comes to their depth, meaning how many properties inherit or include values from other properties. Or, to put it in another way, we don’t put custom properties inside each other very often. One reason might be that we are still mostly treating CSS custom properties like constants. We mostly use them as replacements for fixed values that we only have to define in one central place, which often happens to be, you guessed it, :root. Leaving this habit behind, however, and understanding that custom properties can do so much more is the key to unlocking their full potential.

To give you just one example derived from Lea’s talk, you can use custom properties locally that are basically like private variables but based on a global variable. Sounds complicated? Imagine a simple button component with a background color stored in a global custom property:

/* tokens.css */
:root {
	--color: hsl(160, 100%, 75%);
}

/* button.css */
button {
  background-color: var(--color);
}

This would work well in general and would also inherit the color value nicely into the button component. In a design system (or a modular website), this is exactly what you want. However, it is always a good idea to provide a fallback for custom properties, also because the component would then still work well even without the --color property being defined. With a local custom property that is based on the global property but already has the fallback baked in, you maintain the flexibility of being able to manipulate the color from the outside of the component. At the same time, you make the implementation much more robust:

/* tokens.css */
:root {
	--color: hsl(160, 100%, 75%);
}

/* button.css */
button {
  --_color: var(--color, black)
  background-color: var(--_color);
}

If we now use the button component within a system that provides a --color, the button has a background in the color we defined. If not, the component still works and uses the fallback color, black, instead. Inside of our component, we can use the local custom property (written with an underscore, again a convention) to define hover styles, borders, box-shadows, and more. And, we can still manipulate or set the --color variable from the outside of our component without breaking it. As Lea puts it: CSS variables effectively allow us to create a higher-level styling API.

This is just one powerful example which shows that combining global with local properties can lead to much more flexible and robust CSS. So while it might be still a good idea to define your global custom properties on :root as a best practice, also try to think beyond that. You can use CSS custom properties everywhere all at once.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK