Proposal: migrating to using design tokens in the product
Intent
Using design tokens within the product aims to:
- Enable color modes (e.g. dark mode) to be set with root level class and without compiling a new stylesheet for each mode
- Abstract color variable definitions (e.g. local SCSS variables) to a single-source-of-truth for all consumers
- 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-900or$text-color - Old color usage CSS custom properties for
page_bundlesare replaced with semantic design tokens - SCSS color functions (e.g.
rgba($gray-900, 0.5)anddarken($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, anddark_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:
- Replace
var(--border-color, ...)withvar(--gl-border-color-default)and remove--border-colormanual CSS custom property definition fromdark_mode_overrides.scss - Replace
$border-colorSCSS variable withvar(--gl-border-color-default)CSS custom property and remove SCSS variable definition fromvariables.scss-
Note: there will likely be color functions using
$border-colore.g.darken($border-color, ...)which it may be possible to replace withborder: ... var(--gl-border-color-strong)(to validate)
-
Note: there will likely be color functions using
- Replace
border: ... $gray-100withborder: ... var(--gl-border-color-default) - Replace
border: ... $gray-50withborder: ... var(--gl-border-color-subtle) - Replace
border: ... $gray-200withborder: ... var(--gl-border-color-strong) - 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-variablee.g.$gray-500 -
text-utility-classe.g.gl-text-secondary,gl-text-gray-500etc. -
color-css-propertye.g.--gray-500 -
bootstrap-text-color-utility-classe.g.text-primary -
color-function-literale.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