Proposal: migrating to using design tokens in the product

Intent

Using design tokens within the product aims to:

  1. Enable color modes (e.g. dark mode) to be set with root level class and without compiling a new stylesheet for each mode
  2. Abstract color variable definitions (e.g. local SCSS variables) to a single-source-of-truth for all consumers
  3. Remove previously used color values (e.g. $gray-900) which invert for dark mode

Outcomes from migrating to design tokens may look like:

  • Colors are defined using CSS custom properties either using in styles (e.g. var(--gl-text-color-default)) or Tailwind generated utility classes (e.g. gl-text-default)
  • Old color usage is replaced with semantic design tokens to provide meaning to color usage and shift color mode values to design decisions from previous inverted behaviour e.g. var(--gl-text-color-default) instead of $gray-900 or $text-color
  • Old color usage CSS custom properties for page_bundles are replaced with semantic design tokens
  • SCSS color functions (e.g. rgba($gray-900, 0.5) and darken($gray-10, 10%)) are replaced with intentional design decisions with design token values
  • Remove manually defined SCSS variables or CSS custom properties (not generated from design tokens) from variables.scss, _dark.scss, and dark_mode_overrides.scss

What to use and when

CSS custom properties should be prioritised over SCSS variables when using design tokens to enable intent point 1.

Where a color intentionally does not change between color modes it may be appropriate to use a constant color value e.g. color.neutral.500 either as a CSS custom property or SCSS variable to migrate away from old colors. However, this should only be a stop-gap as a semantic token should be provided to prescribe meaning in usage.

Validating

When updating existing color usage to use design tokens it's important to ensure we maintain the existing visual rendering of default and dark color modes wherever possible. Some values may differ from existing usage, e.g. text.color.default uses color.neutral.800 instead of $gray-900, this is expected.

Toggling gl-dark color mode

To validate design token CSS custom properties being used we can toggle the gl-dark class on root <html> element. This will swap CSS custom property defintions between tokens.css and tokens.dark.css. Components/styles which are using custom properties or Tailwind classes should update without page refresh.

The following JavaScript bookmarklet can be used to easily toggle the gl-dark class:

javascript:(function() { document.documentElement.classList.toggle('gl-dark'); })();

$white renders differently

Currently the $white SCSS variable is updated to #28272d in dark mode. This is lighter than background.color.default (color.neutral.950) which is #18171d.

This leads to different behaviours between default and dark mode where a surface inline with content can appear elevated. Using background.color.default will ensure consistent rendering between modes. However this needs to be validated in dark mode to ensure visual rendering is as expected and surrounding content has appropriate boundaries.

Strategy

Focus on migrating colors for background.color, border.color, text.color, and icon.color.

Note: icon color can be set with --gl-icon-color-* CSS custom properties, gl-fill-icon-* Tailwind classes, or with <gl-icon variant="">. Where possible we should prioritise the variant prop on the component and remove any contextual styles added with classes or CSS.

Splitting up work

To minimise effort in validating visual changes in MRs it might be simplest to update like-for-like properties and values across stylesheets,rather than individual views, for example:

Vertical

Variables Utilities Application Page bundle Page bundle
text text text text text
background background background background background
border border border border border
icon icon icon icon icon

Horizontal

Variables Utilities Application Page bundle Page bundle
text text text text text
background background background background background
border border border border border
icon icon icon icon icon

This might look something like the following:

  1. Replace var(--border-color, ...) with var(--gl-border-color-default) and remove --border-color manual CSS custom property definition from dark_mode_overrides.scss
  2. Replace $border-color SCSS variable with var(--gl-border-color-default) CSS custom property and remove SCSS variable definition from variables.scss
    1. Note: there will likely be color functions using $border-color e.g. darken($border-color, ...) which it may be possible to replace with border: ... var(--gl-border-color-strong) (to validate)
  3. Replace border: ... $gray-100 with border: ... var(--gl-border-color-default)
  4. Replace border: ... $gray-50 with border: ... var(--gl-border-color-subtle)
  5. Replace border: ... $gray-200 with border: ... var(--gl-border-color-strong)
  6. Find any other custom border SCSS variable definitions and evaluate if these can be replaced with design tokens

Evaluating migration

We currently have rules in gitlab-code-scanner to evaluate:

  • color-sass-variable e.g. $gray-500
  • text-utility-class e.g. gl-text-secondary, gl-text-gray-500 etc.
  • color-css-property e.g. --gray-500
  • bootstrap-text-color-utility-class e.g. text-primary
  • color-function-literal e.g. darken($white, 0.5), rgba($white, 0.5) etc.

With the migration to design tokens we should see a reduction in all of these rules which can be viewed on the history tab

Edited by 🤖 GitLab Bot 🤖