stylex icon indicating copy to clipboard operation
stylex copied to clipboard

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

Open zetavg opened this issue 1 year ago • 15 comments

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, SAP Fiori 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:

// 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:

// 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:

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 (
    <>
      <div {...stylex.props(indigoTheme)}>
        <h2>Button with Indigo Theme</h2>
        <p>
          In this div, a theme is applied which overrides <code>systemColors.primary</code> to <code>colorPalette.indigo</code>.
        </p>
        <Button label="Indigo button" />
      </div>
    </>
  );
}

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:

: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:

: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.

zetavg avatar Jun 08 '24 01:06 zetavg