An Intro to Design Tokens, Style Dictionary, and Runtime Theming¶
The traditional mechanism for compiling MFEs has been for each MFE to pull in the common Paragon SCSS code
from @edx/paragon
, and the command branding SCSS code from @edx/brand
into the MFE itself and then compile it
together to produce the CSS files for the MFE.
There are some obvious flaws in this approach, the big ones being:
- Each MFE will compile the same core Paragon theme code, leading to wasted time
- Each MFE will include an independent copy of the Paragon theme code, leading to duplicated space
- For each MFE a user will have to download mostly similar CSS code, leading to larger slower downloads
- For any small change to the theme, each MFE will have to be compiled again, again leading to slower builds and deployments
There is now a new approach being worked on in the platform which will eliminate these issues. In this new system, you have a core set of Paragon CSS files that can be served by a CDN and consumed by all MFEs. These are reused across all MFEs so if a user visits one, they will have a cached copy and other MFEs will load faster. On top of this core set each MFE can include its own styling in a separate sheet.
This wasn't possible earlier since each MFE needed to load the Paragon theme to be able to load SCSS variables and use them in the MFE. However, if Paragon supports CSS variables, then MFEs can simply reference variables that are defined in another CSS file without needing to have it available at build time.
On top of this, this new system includes a mechanism using Design Tokens provided using "Style Dictionary" to build a CSS variable-based theme on top of the existing base Paragon theme. That's a lot of jargon for, you can use a new system to build a CSS file that has all the relevant CSS variables needed to customise the branding of a site. The location of this CSS file can be supplied via the MFE Config API, so you can have each site or MFE have a different theme.
We've now introduced a couple of new terms, so let's go over those briefly.
- CSS Custom Properties or CSS Variables
- Design Tokens
- Style Dictionary
CSS Custom Properties¶
One of the major features SCSS had over CSS was the ability to create variables that can be reused in multiple places.
Instead of specifying a colour like #851C54
in a hundred different places, you can specify $my-color: #851C54
and
then use $my-color
wherever you wanted.
Now CSS has a way of doing this using CSS custom properties. This relatively new feature allows you to specify values that can be reused across your stylesheets. The same example as before using pure CSS would be:
1 2 3 4 5 6 7 |
|
The advantage here is that if you separate out all your definitions of CSS variables into a separate stylesheet, you can retain most of your CSS and radically modify your site by simply swapping out the variable definitions.
With the new Paragon system, every aspect of Paragon that can be customised has a CSS variable you can set to override
it.
For instance, you have: --pgn-color-btn-disabled-bg-inverse-outline-primary
. As you might be able to tell, it is the
background colour to use for a button that is disabled and has the outline-primary type.
If you want, you can hand-craft a theme by just providing all the variables like able that need to be overridden. However Paragon also provides tools to simplify this using design tokens provided in the form of style dictionaries.
Design Tokens¶
Design tokens is just a fancy way of saying "variables related to UX design". Its design decisions translated to data so that it can be consumed or translated in multiple ways.
For instance, if you want to represent the background colour of the header of a pop-over, it could be represented by the
token color.popover.header.bg
. This token could be given a value of #851C54
and then this value can be used when and
where needed.
What you need is some standard way to organise these tokens, supply their value and then convert it into the form you
need. For example, you could use the above token in a website if it's converted to a CSS variable
like --color-popover-header-bg
. It could even be rendered to SCSS for another application, or rendered to a form that
Android or iOS apps understand, so they can be themed as well.
Essentially, design tokens are meant to be a source of truth and then rendered into other formats as needed. Paragon uses a system called Style Dictionary to specify these variables and render them to CSS variables.
Style Dictionary¶
Design tokens are a concept that can be implemented in many ways using many formats. Style Dictionary is a web-focussed implementation that primarily uses JSON files to represent the tokens in a hierarchical manner.
Style Dictionary needs to be given a folder filled with JSON files that have token values, and it will merge them all and produce an output in multiple form. Paragon, however, only focusses on CSS for now, so the tokens are compiled into CSS variables.
Taking the previous example, if you wanted to specify the value for the color.popover.header.bg
token, you'd specify
it
as:
1 2 3 4 5 6 7 8 9 10 11 |
|
This might seem a bit verbose, but the power of this system really kicks in when you consider that you can reference
other variables. For instance, if you wanted to say that, the above colour should be the same as the background colour
of a
secondary button, you could set the value to "{color.btn.bg.secondary}"
and the style dictionary system will
automatically fills in the value. It can also transform values, such as darkening or lightening colours.
The output produced will be CSS files with the variable values from the tokens. So the above colour would be rendered as
--pgn-color-popover-header-bg: #851C54;
. These CSS files can then be uploaded to a CDN and specified in site or tenant
config, so they will be provided via the MFE config API.
MFE Config for themes¶
Once you have a theme built, you can load it in an MFE by providing its URL in site or tenant configuration. The MFE will then fetch these values from the MFE Config API, and dynamically load them at runtime. This has a few benefits:
- The theme can now be varied along the same axis as the MFE config API, i.e. on a per-site, and per-mfe level. So you can technically have a different theme for each MFE and site.
- Changes to the theme can be deployed without needing to redeploy an entire MFE. This can be done by either changing the URL in the config, or by changing the contents of the CDN.
The MFE config API needs to have the following structure for the URLs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
In the above config, with core.urls.default
we are providing a link to the upstream Paragon core theme which has the
bulk of the CSS code. If our theme has an override for this, you can specify that using core.urls.brandOverride
. After
that you have variants.light.urls.default
which has the default upstream version of the light
theme variant. The
idea is to eventually have a dark
variant as well and potentially load the correct one based on preferences.
Currently, only the light
variant is supported.
The $paragonVersion
above is special sytax, so the correct version of Paragon will automatically be filled in there
so if different MFEs are using slightly differnt versions, that's OK.
In siple-theme we now have a CI setup so each merged PR that modifyes the theme will automatically build the CSS and push it to an S3-compatible storage. This means updating a theme can be as simple as making a PR and mergning it. Rollback can be as simple as reverting a PR and mergning.