Re: [community-group] Native modes and theming support (#210)

This is my very first community involvement here so hopefully it adds something useful to the discussion.

Just a little background, I'm writing a token management and code generation tool that enables fully themeable design system. The focus is pretty much on the web ecosystem right now and I hope to expand it to other platforms in the future.

Let's first focus on conditional token values within a product. As mentioned by many others, these conditions comprise modes and system settings.

In my proposed format, these conditions are also defined as design tokens. They can then  be used to generate PostCSS [custom media](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media) & [custom selectors](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-selectors), SASS mixins, [Tailwind variants](https://tailwindcss.com/docs/plugins#adding-variants), TypeScript type definitions etc.

Unlike other design tokens which can be nested arbitrarily in groups, condition tokens are defined in a more structured manner.

Each alternative token value is itself a design token, defined using the keyword `$set`. They can refer to defined condition tokens using the keyword `$condition`.

```json5
{
  condition: {
    color_scheme: {
      light: {
        $value: '[data-color-scheme="light"]',
      },
      dark: {
        $value: '[data-color-scheme="dark"]',
      },
    },
    contrast: {
      standard: {
        $value: '[data-contrast="standard"]',
      },
      more: {
        $value: '[data-contrast="more"]',
      },
    },
    motion_pref: {
      none: {
        $value: '@media (prefers-reduced-motion: no-preference)',
      },
      reduced: {
        $value: '@media (prefers-reduced-motion: reduce)',
      },
    },
  },
  color: {
    primary: {
      $set: [
        {
          $condition: {
            color_scheme: 'light',
            contrast: 'standard',
          },
          $value: '{color.purple.80}',
        },
        {
          $condition: {
            color_scheme: 'light',
            contrast: 'high',
          },
          $value: '{color.purple.90}',
        },
        {
          $condition: {
            color_scheme: 'dark',
            contrast: 'standard',
          },
          $value: '{color.purple.20}',
        },
        {
          $condition: {
            color_scheme: 'dark',
            contrast: 'high',
          },
          $value: '{color.purple.10}',
        },
      ],
    },
  },
}
```

Translated to CSS:

```css
:root[data-color-scheme='light'][data-contrast='standard'] {
  --color-primary: var(--purple-80);
}

:root[data-color-scheme='light'][data-contrast='more'] {
  --color-primary: var(--purple-90);
}

:root[data-color-scheme='dark'][data-contrast='standard'] {
  --color-primary: var(--purple-20);
}

:root[data-color-scheme='dark'][data-contrast='more'] {
  --color-primary: var(--purple-10);
}
```

The proposal can be extended to support component variants, a concept that has been adopted by various CSS libraries. There are parallels between the 2 concepts - conditions are visual variations of a product while component variants are visual variations of a component. One is global, the other local.

```yaml
app conditions
  - color scheme mode:
    - light
    - dark
  - contrast mode:
    - standard
    - more

button variants
  - intent:
    - primary
    - secondary
  - style:
    - filled
    - outline
```

Just like a semantic token can have alternative values under different conditions, a component token can have alternative values for different component variants.

In CSS, it looks like this:

```css
.button[data-intent='primary'][data-style='filled'] {
  --button-background-color: var(--color-primary);
  --button-border-color: transparent;
}

.button[data-intent='primary'][data-style='outline'] {
  --button-background-color: transparent;
  --button-border-color: var(--color-primary);
}

.button[data-intent='secondary'][data-style='filled'] {
  --button-background-color: var(--color-secondary);
  --button-border-color: transparent;
}

.button[data-intent='secondary'][data-style='outline'] {
  --button-background-color: transparent;
  --button-border-color: var(--color-secondary);
}
```

Similar to conditions, component variants are defined as design tokens. A component token can have multiple values defined using the keyword `$variant`.

```json5
{
  // component tokens are specified under "component" group
  component: {
    button: {
      // define variants
      $variant: {
        intent: {
          primary: {
            $value: '[data-intent="primary"]',
          },
          secondary: {
            $value: '[data-intent="secondary"]',
          },
        },
        style: {
          filled: {
            $value: '[data-style="filled"]',
          },
          outline: {
            $value: '[data-style="outline"]',
          },
        },
      },
      background_color: {
        $set: [
          {
            $variant: { intent: 'primary', style: 'filled' },
            $value: '{color.primary}',
          },

          // ... other variations

          // combining with $condition
          {
            $condition: { contrast_pref: 'forced' }, // resolves to "@media (forced-colors: active)"
            $variant: { intent: 'primary', style: 'filled' },
            $value: 'ButtonText',
          },
        ],
      },
    },
  },
}
```

Adopting `condition` and `variant` thus creates more opportunities for design system automation. In the context of websites and web applications, since a component's visual styling can be represented almost entirely by CSS custom properties generated from design tokens, the component's core CSS can remain stable across products/frameworks.

```css
/* stable, fully themeable component CSS */
.button {
  background-color: var(--button-background-color);
  border-color: var(--button-border-color);
}

/* generated from design tokens */
.button[data-intent='primary'][data-style='filled'] {
  --button-background-color: var(--color-primary);
  --button-border-color: transparent;
}

.button[data-intent='primary'][data-style='outline'] {
  --button-background-color: transparent;
  --button-border-color: var(--color-primary);
}

.button[data-intent='secondary'][data-style='filled'] {
  --button-background-color: var(--color-secondary);
  --button-border-color: transparent;
}

.button[data-intent='secondary'][data-style='outline'] {
  --button-background-color: transparent;
  --button-border-color: var(--color-secondary);
}
```

Theming across multiple products on multiple platforms can be achieved simply by combining different token sources.

```yaml
Brand A web:
  - core-tokens
  - web-tokens
  - brand-a-tokens
  - brand-a-web-tokens

Brand B web:
  - core-tokens
  - web-tokens
  - brand-b-tokens
  - brand-b-web-tokens
```

As the major focus of the spec is platform independence, I understand it is controversial to have platform-specific concepts in the design token format. For a code generation tool, I argue this is necessary as it allows using platform capabilities to the fullest. Overlooking platform-specific concepts, design tokens cannot fully represent all the design decisions within the system. Those instead need to be handled via tooling and source code, thereby reducing some degree of visibility and control.

Last but not least, since the tool is fairly new, I would very much appreciate it if you could [try it out](https://github.com/universse/theminglayer) and share your feedback. Thank you very much.

-- 
GitHub Notification of comment by universse
Please view or discuss this issue at https://github.com/design-tokens/community-group/issues/210#issuecomment-1867795781 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Friday, 22 December 2023 15:10:01 UTC