facebook / facebook/stylex

(Reactive Variables) Support of swapping underlying variables while using hierarchical variable references

Open
#601 15 comments 4 reactions 0 assignees View on GitHub
enhancement
Dominant language
JavaScript
Stars
10.3k
Forks
481
Avg merge
3d 8h
Merged PRs (30d)
13

Description

### Describe the feature request

> Update: as @aspizu mentioned, see #566 for a more concise description of the same situation.

## Background
In some design systems such as [Material Design](https://m3.material.io/foundations/design-tokens/how-to-read-tokens#efa90c6e-92da-4af3-8031-f0520540df25), [SAP Fiori](https://experience.sap.com/fiori-design-web/design-tokens/#token-hierarchy) and many more, there’s this concept called *Design Token Hierarchy*, where high level and more semantic tokens can reference low-level tokens.

(Image from https://stefaniefluin.medium.com/the-pyramid-design-token-structure-the-best-way-to-format-organize-and-name-your-design-tokens-ca81b9d8836d)

To my knowledge, we can define hierarchical tokens in StyleX like this - for system and reference tokens:

```ts
// tokens.stylex.ts

import * as stylex from '@stylexjs/stylex';

/** Reference tokens for color palette */
export const colorPalette = stylex.defineVars({
blue: '#007AFF',
indigo: '#5856D6',
white: '#F2F2F7',
// ...
});

/** System tokens for color */
export const systemColors = stylex.defineVars({
primary: colorPalette.blue,
onPrimary: colorPalette.white,
// ...
});
```

And for component tokens:

```ts
// components/Button/tokens.stylex.ts

import * as stylex from '@stylexjs/stylex';

import { systemColors } from '../../tokens.stylex';

export const buttonTokens = stylex.defineVars({
primaryColor: systemColors.primary,
primaryLabelColor: systemColors.onPrimary,
// ...
});
```

Which, for example, constructs a token hierarchy as:

```
(Component Token) (System Token) (Reference Token)

buttonTokens.primaryColor <─── systemColors.primary <─── colorPalette.blue <─── '#007AFF'
```

## The Problem
Using themes that override tokens (variables) that are referenced by other tokens (variables) might not work as expected. For example, if we want to apply a different color theme:

```jsx
import * as stylex from '@stylexjs/stylex';

import Button from './components/Button';
import { colorPalette, systemColors } from './tokens.stylex';

const indigoTheme = stylex.createTheme(systemColors, {
primary: colorPalette.indigo,
});

function App() {
return (
<>


Button with Indigo Theme



In this div, a theme is applied which overrides systemColors.primary to colorPalette.indigo.





);
}
```

We may expect an indigo button:

```
(Component Token) (System Token) (Reference Token)

buttonTokens.primaryColor <─── systemColors.primary <─╮x colorPalette.blue <─── '#007AFF'

╰─ colorPalette.indigo <─ '#5856D6'
```

But it’s not the case. The button will still be blue, due to how CSS variables work:

```css
:root {
--systemColors_primary: var(--colorPalette_blue);
/* ... */ │
} ╰───────────────────────────────╮

:root { ↓
--buttonTokens_primaryColor: var(--systemColors_primary);
/* ... */ │
} ╰─────────────────────────────╮

.indigoTheme { │
--systemColors_primary: var(--colorPalette_indigo); │
/* ... */ │
} ╭──────────────────╯

.button { ↓
color: var(--buttonTokens_primaryColor);
/* ... */
}
```
(In reality, StyleX will generate unique IDs for class names and variable names; here, we write them as recognizable names just for readability.)

To make this work, we may need to modify StyleX to re-declare all the variables that reference overwritten variables when declaring themes, so that:

```css
:root {
--systemColors_primary: var(--colorPalette_blue);
/* ... */
}

:root {
--buttonTokens_primaryColor: var(--systemColors_primary);
/* ... */
}

.indigoTheme {
--systemColors_primary: var(--colorPalette_indigo);
╰──────────────────────╮
/* Re-declare */ ↓
--buttonTokens_primaryColor: var(--systemColors_primary);
/* ... */ │
} ╰─────────╮

.button { ↓
color: var(--buttonTokens_primaryColor);
/* ... */
}
```

## Repro
I made a repro here: https://github.com/zetavg/stylex-token-hierarchy-esbuild-example. It’s based on the `esbuild-example`.

---

I’m not sure if this is a thing that can be improved, is there already a solution, or it’s designed this way on purpose.

Instead of re-declaring CSS variables, there’s another solution in my mind which is done by adding class names, but I’m unsure about the performance and scalability concerns with either way.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.