We got kickass reactive variables
and we are still treating them
as glorified constants
Custom properties [reached full browser support 5 years ago](https://caniuse.com/css-variables).
Why are we still merely scratching the surface?
Today’s talk: A story in three acts
The Outlined Button
This is a simplified flat button whose text color becomes its background color on hover, a common effect.
Note the repetition required to specify a color variation. Let's use CSS variables to eliminate that!
After rewriting with CSS variables, only a single `--color` declaration is sufficient to create a color variation of the entire component.
However, the fallbacks are getting quite repetitive.
Although the syntax allows us to use a different fallback in every usage,
in most cases, we don't need that.
Repeating the fallback value over and over is not [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
What can we do to reduce that duplication?
Another benefit of custom properties is *encapsulation*.
We can change how styling works completely, and as long as we use the same custom properties
anyone using and styling our component doesn't need to change a thing.
CSS variables are just regular properties
What does that mean?
- They are scoped on the element itself, not on curly braces (lexical vs dynamic scoping)
- Can be set via inline styles or even JS setting these inline styles
- As we will see later on, they are actually *inherited properties*
Pseudo-private variables are a convention: prefix custom properties by `_` to indicate they are for internal use.
Useful for baking in fallback values, but also other things.
CSS Variables allow us to create a higher level styling API
Provide encapsulation by exposing a higher level styling API. We can change the underlying CSS and the API we've exposed will continue to work.
Theming becomes independent of CSS structure.
You may also remember a `CSS.registerProperty()` JS API.
This came earlier, but at this point support for `@property` is nearly identical.
The main reason to use `CSS.registerProperty()` these days is because of its richer error reporting.
To fully understand how this works, we need to understand the concept of Invalid At Computed Value Time (IACVT).
In pre-variables CSS, When the parser encounters a value it doesn't understand, it can just throw it away.
This is how we usually get fallbacks: we write two declarations, one with the newer value, and one with older CSS,
older browsers ignore the newer CSS, newer browsers override the first declaration with the second.
Can we do the same thing with variables?
The thing is, variables are resolved later, at computed value time.
Until then, the browser needs to assume any declaration that includes a variable anywhere could potentially be valid.
So, here, the first background declaration would just be thrown away.
IACVT comes into play not just when the browser doesn't understand a property value after substitution,
but also when the property value is blank, when one or more variable values are missing,
or when there are cycles. Also, the fallback can make a declaration IACVT as well.
Can we do the same thing with variables?
The thing is, variables are resolved later, at computed value time.
Until then, the browser needs to assume any declaration that includes a variable anywhere could potentially be valid.
So, here, the first background declaration would just be thrown away.
Credit to [Ana Tudor](https://twitter.com/anatudor/status/1399849494628425734) for discovering this trick.
[CSS Values 4](https://www.w3.org/TR/css-values-4/) includes a
[`round()` function](https://www.w3.org/TR/css-values-4/#round-func) for this,
that can also do lengths etc and rounds by arbitrary steps,
but it is not currently implemented anywhere.
Future:
/* Round: */
counter-reset: p round(var(--p), 1);
/* Floor: */
counter-reset: p round(down, var(--p), 1);
/* Ceil: */
counter-reset: p round(up, var(--p), 1);
You can use linear mapping to do conditionals in a more readable way than space toggle.
Essentially you're just mapping the 0 to 1 range
and ignoring all values that are not 0 or 1.
Interested in finding out more about ways to work around conditionals in CSS?
Here are a few good articles:
- [DRY Switching with CSS Variables: The Difference of One Declaration](https://css-tricks.com/dry-switching-with-css-variables-the-difference-of-one-declaration/) by Ana Tudor
- [DRY State Switching With CSS Variables: Fallbacks and Invalid Values](https://css-tricks.com/dry-state-switching-with-css-variables-fallbacks-and-invalid-values/) by Ana Tudor
- [Logical Operations with CSS Variables](https://css-tricks.com/logical-operations-with-css-variables/) by Ana Tudor
- [CSS Switch-Case Conditions](https://css-tricks.com/css-switch-case-conditions/) by Yair Even Or
Linear mapping allows the same variable to control multiple things, continously or discretely
Useful for exposing a nice API or simply for minimizing the number of properties you need to register
Using variables for data also helps make code more readable.
It makes much more sense to change a `--look` variable from
`0` to `1` than from `25px` to `75px`,
and affords more flexibility for redesigns.
However, this means we need to map that number to the value we need
at the point of usage. How can we do that?
Range mapping allows you to decouple input from output
Conclusion
Is it all a steaming pile of shit hacks?
API simplicity>Implementation simplicity
Yes, a lot of these were hacks. That doesn't mean they are always a bad idea.
It is often preferable to trade higher implementation complexity to gain lower API complexity.
Implementation can always change; it is much harder to change an API.
Overcomplicated APIs cause a lot more collective pain than overcomplicated implementations,
because there are always more API users than API developers.
So do what you gotta do to expose a nice API.
Write components whose CSS properties are independent and each control one conceptual thing.
Write components that can work with whatever CSS you throw at them.
Write components whose CSS properties can be modified at any point without breakage.
And if you need to employ hacks to do these things, so be it.
This is not a license to write crappy code.
This is about the many cases where you need to make a tradeoff between the two.
But ultimately, every case is different, and the final decision is yours.