Alternative titles: Dependentdesign tokens, computed theme values
For the past few weeks I've been working on updating the theme within my
@ds-pack/components
package,
specifically around the colors that I'm using within the theme and the ones that
map to "functional roles" (e.g. primary
, secondary
, ... etc). Through this
work I've been exploring a few different patterns that I wanted to share more
widely.
Before diving into the two things I've been tinkering with I want to step back
and define the problem space a bit, specifically with this work I've been
thinking about how to enable consumers of the above components package to theme
the system without needing to re-construct the whole theme
object that the
components use.
In a traditional theme, this customization might be fairly basic by using object spread to override some values:
This works really well for flat theme shapes (which might be worthy of another blog post in itself), but for nested themes, this soon gets out of hand:
Another complication that arises when customizing a theme is dependent theme
values, let's say that the theme shape also contains some styles for different
variants of a component (e.g. styled-system's
variant
), and those styles use the same values from other parts of theme:
If the system defines the theme like above, and a consumer wants to change the
primary color to cornflowerblue
, the consumer may not know that they need to
change the buttons.primary.color
value as well.
These two issues:
- Less than ideal ergonomics for overriding themes
- Dependent theme values don't react to overrides
make theme overriding a particular indersting challenge.
So let's finally dive into some of the ideas I've been working on, specifically two concepts:
Theme Getters
The first idea that I started workshopping was to use a
getter
to define values within theme that can reference other values within the theme.
I didn't mention above, but a common workaround I've seen for the second problem noted is defining the theme primitives outside of the scope of the theme object, and referencing those later:
However this only solves the referencing issue for the file in which the theme is defined (frequently that is within the source package for the system). With getters however, you move that derivation time to runtime rather than module load time:
Now, the button primary color inherits the primary color specified on the theme, allowing us to override only that value and have the button styles inherit those changes:
"Token References"
The second idea I've been working on is around using token references[1], popularized by Stitches and used by System Props.
Essentially using the same syntax that you might use on a traditional Box
component:
within the theme shape itself:
With this method, we've moved the derivation from the reference of the value
with the getter method to the callsite of this themer
function (which has a
few trade-offs depending on how dynamic your theme needs to be).
To experiment with this more deeply, I built a small package that you can try
out: @ds-pack/themer
.
I'd love to hear if you've found interesting solutions to these problems, feel free to reach out on twitter or via email.