Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: ADR to expose all SCSS variables that MFEs use as CSS variables [BB-6307] #1388

Closed
143 changes: 143 additions & 0 deletions docs/decisions/0018-expose-all-sass-vars-as-css-vars.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
0018 Expose CSS Variables for Consumers of Paragon
##################################################


Status
******

Draft

Context
*******

CSS traditionally did not support variables, which meant that to reuse colour
codes, or standardise margins, paddings, fonts or other styles we needed to use
a CSS pre-processor that supports variables, but removes variables from the
output CSS. This meant that any changes to a variable would require recompiling
the CSS from the source SCSS.

CSS now supports its own form of variables, and they now have `broad support
across browsers`_ . Since these are natively supported by CSS these can be used
at run time, and don't need a compilation step. They offer a couple of
advantages over SCSS variables such as:

- they can be changed at runtime by changing the stylesheet, and can be
mainpulated using JavaScript.
- they can be scoped to a particular CSS class or media query.

SCSS/SASS still offers a number of other advantages such as nested classes,
mixins, functions, and utilities that are currently not available with CSS.

All Open edX MFEs use Bootstrap via Paragon for styling. Each MFE builds its
own stylesheet which is a combination of Bootstrap, Paragon, and some MFE-specific
styling.

Currently the SCSS code is structured as follows:

- At the core we have Bootstrap
- Then we have Paragon, which uses Bootstrap and react-bootstrap
- Then we have the branding package which has SCSS files that can override
Paragon defaults and add additional SCSS code
- Then we have MFEs that import from the branding package and Paragon, and
usually hav some of their own SCSS code

Paragon uses Bootstrap variables, and the branding theme uses Paragon/Bootstrap
variables, any changes to any variables mean that the entire codebase needs to
be rebuilt. However, this need not be the case, if the dependencies between
these packages is expressed via CSS variables instead of SCSS variables, then
any changes to values will be picked up at runtime. Such a setup would allow
building Bootstrap + Paragon + the branding package into a stylesheet that
contains all the variables MFEs need so they can use the stylesheet directly
and not need recompilation if this common stylesheet changes.

This ADR only concerns itself consumers of Paragon, and as such still expects
Paragon to continue using SCSS variables itself.

If we want to be able to support a common pre-built core stylesheet that can be
xitij2000 marked this conversation as resolved.
Show resolved Hide resolved
used by all MFEs and deployed independently of MFEs, then MFEs cannot use any
SCSS variables from Paragon + Bootstrap since in that case any changes to the
core theme will require a rebuild of each MFE, and as such these variables
should instead be used as CSS variables so that they can be looked up at
runtime rather than build time.

Bootstrap already exports a number of CSS variables (these can be found in
`this document for Bootstrap 4.x`_), however they don't cover all usecases. For
instance, usage of colour variables like `primary-400`, or `light-300` are
abundant in MFEs, however these are not exported by Bootstrap even though they
are available as SCSS variables.

Decision
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in full support of this ADR and decision. As you mentioned, we can start moving towards CSS variables in Paragon since Open edX officially no longer supports older browsers like IE 11, per our shared @edx/browserslist-config.

There are still some open questions around the Bootstrap 5 upgrade, which is why it hasn't necessarily been prioritized yet. I agree with the approach to namespace all the Paragon specific CSS variables with --pgn so they are distinct from the CSS variables provided by Bootstrap, if/when we do upgrade.

For what it's worth, adopting CSS variables is on the Paragon Working Group team's roadmap, likely in a few weeks, as part of our effort to adopt a more scalable solution to design tokens, using something like style-dictionary (demo), where design tokens are drafted as JSON files in a standard format, and then style-dictionary transforms/exports those design tokens into various other formats accepted by various platforms (e.g., JS, CSS variables, SCSS, iOS, Android, Figma, Sketch, etc.). We are likely planning to build out a prototype of this concept for design tokens in the next month or two through the Blended Development project BD-39 that I'm leading with Raccoon Gang.

What is the timeline you're looking for here in an implementation? Were you planning on making the contribution to Paragon to create these CSS variables yourself? If so, can you expand on this decision in your ADR to help explain the plan for how we'll manage the creation of these CSS variables in more detail?

I'm planning to draft an ADR for Paragon about style-dictionary that refers to this PR, as a way to implement design tokens and automation around generation of CSS and SCSS variables (among other platforms as well).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamstankiewicz

What is the timeline you're looking for here in an implementation? Were you planning on making the contribution to Paragon to create these CSS variables yourself? If so, can you expand on this decision in your ADR to help explain the plan for how we'll manage the creation of these CSS variables in more detail?

I am definitely interested in contributing towards this myself, and taking help from others from OpenCraft. I'll expand on how these SCSS variables can be created in this ADR to explain my proposal for how we can get started.

Last I read up about experiments with css in JS my understanding was that that approach was abandoned. The style-dictionary approach seems a bit different. Is there any place I can read more about how it would be used for MFEs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read up on style-dictionary the use case that they are trying to solve is maintaining the constant style across platforms while our need is to make the style changes independent of the build process, right?

The other point I have is the need to use CSS variable will help us to reduce/eliminate the need to use SCSS preprocessor. While if we try to migrate/use style-dictionary we will again fall into the parser trap. Where our build time is dependent on how fast the parser is.

Do we really need that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think eliminating SCSS is a goal here. Even if SCSS takes time to build it does provide a lot of benefits. Additionally, there will still need to be a pre-processor involved to deduplicate and minifiy the css code.

This ADR is not currently too concerned about the build time for paragon or common theme. The idea is to make it possible to inject variables after the theme is built so that a lot of common theming usecases can be accomplished without recompiling dozens of MFEs.

This is a first step I see. Going from needing to rebuild 12 MFEs to rebuilding one theme. The next step can then involve reducing how much of that theme needs to be rebuilt till we reach a point where a single css file having all the necessary variables is all that's needed, and can be even generated dynamically.

Copy link
Member

@adamstankiewicz adamstankiewicz Jun 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(apologies in advance for the long response 😅)

Last I read up about experiments with css in JS my understanding was that that approach was abandoned. The style-dictionary approach seems a bit different. Is there any place I can read more about how it would be used for MFEs?

Yes, CSS-in-JS has been largely dropped for now. That said, I was mostly asking just to get any alternatives considered briefly written as part of this ADR for context 😄

There isn't currently anywhere currently where this is written down, except for these notes from a recent Paragon Working Group meeting about design tokens (raw notes from the discussion). I'm hoping to draft an ADR around style-dictionary and propose it to the Frontend Working group in the near future with more details and the rationale.

read up on style-dictionary the use case that they are trying to solve is maintaining the constant style across platforms while our need is to make the style changes independent of the build process, right?

The purpose of a design system is exactly that, to maintain consistent style and patterns across platforms for a unified brand user experience 😄 That doesn't mean it can't be theme-able as well.

Design tokens, as intended, represent the fundamental atomic building blocks of a design system (e.g., spacing, typography, color, etc.). Today, our design tokens are largely implemented as SCSS variables. The W3C design token community group spec describes the context in a bit more detail.

Largely, the proposal for style-dictionary is not define our design tokens as either CSS variables or SCSS variables, but to instead define them as a standard format for a design token in JSON. By using style-dictionary we could automatically generate both CSS and SCSS variables representing the same design token values. style-dictionary could also generate the same design token values as CSS utility classes for us in a standardized way, too.

style-dictionary is largely for defining design tokens as a standard format, so that it can be transformed/exported for consumption by a variety of platforms. style-dictionary can also export to a data format that Sketch, and possibly even Figma, can use which is intriguing for collaboration with designs and having truly a single source of truth for our design tokens.

This ADR is not currently too concerned about the build time for paragon or common theme. The idea is to make it possible to inject variables after the theme is built so that a lot of common theming use cases can be accomplished without recompiling dozens of MFEs.

Exactly 😄 From a build time perspective, style-dictionary should only be an issue for Paragon and the common theme itself, but not consumers. The impact style-dictionary may have on consumers is a breaking change in some way such that the SCSS variable names change from what they are now; style-dictionary is just responsible for creating which CSS variables, SCSS variables, and CSS utility classes are available to use (among other potential export formats for mobile apps).

Then, in the Paragon components they would likely start reading from the CSS variables instead of SCSS variables, which can be themed at run-time and is the goal of this current ADR. In other words, style-dictionary (and how we configure it) would be responsible generating the files and output for the public API for theming the Paragon design system, and potentially enable theming with both SCSS and CSS variables depending on the consumers needs is the idea.

At a high level, we would like to make Paragon more platform-agnostic, while maintaining its theming capabilities (e.g., perhaps a web component implementation of the design system component library could be interesting).

So, not entirely sure yet, but I believe style-dictionary would potentially impact MFEs using:

  • SCSS variables. they may/will change to a new standard format, generated by style-dictionary. As an example, our color design tokens might change to a standard format of $pgn-color-warning-900 vs. $warning-900 now (or --pgn-color-warning-900 vs. --warning-900), which with well-documented release notes / migration guide / CLI tool shouldn't be too bad of an upgrade?
  • CSS utility classes. those too might change for a standard format, output by style-dictionary per our configuration (e.g., .pgn-color-warning-900).
  • I'm likely missing something 😄 Again, still some investigation to do through writing up an ADR about style-dictionary and working on a proof-of-concept prototype.

If you want to try it locally, checkout that linked branch, run npm run install-all, and then npm run build_tokens. You'll see it generate a few files from the source design tokens defined as JSON objects in the ./tokens directory:

  • style-dictionary-build/scss/_variables.scss
  • style-dictionary-build/css/variables.css
  • style-dictionary-build/js/variables.js

It'd impact any custom themes (e.g., @edx/brand-edx.org) as they, too, would likely need to migrate to the new standard variables instead of the current ones as well.

We are in the early stages of thinking through the implications of style-dictionary, but I do think it's very much aligned with the goal of this ADR. I believe style-dictionary is largely seen as a way to implement the CSS variables proposed by this ADR in a reasonably scalable, and platform-agnostic way.

********

We will export all theme variables that an MFE could need as CSS variables so
that these can be applyed at runtime rather than build time.

Paragon can create CSS variable exports like: `--pgn-blue`, `--pgn-primary`,
`--pgn-primary-200`` etc. The reason for prefixing with `--pgn` is to isolate
changes to these names in Bootstrap. For example, in Bootstrap 4.x these are
named as `--blue, --primary` etc. However in `Bootstrap 5`_, they are prefixed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason I'd love for Paragon to move away from Bootstrap nomenclature is because consumers often rely on the internals of Bootstrap (classes, mixins, variables, etc), which in theory should be just an implementation detail of the Paragon library, not really exposed to the consumer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link isn't redirect because the link reference is Bootstrap 5.x

Copy link
Contributor

@dcoa dcoa Jul 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is a good idea to find a runtime theming option and this ADR is a good starting point and could show the capabilities of CSS variables for the open edx ecosystem. I support this ADR.

with `--bs-blue, --bs-primary`, this incompatibility can be handled at the
Paragon level whenever it switches from 4.x to 5.x without needing changes from
MFEs.

Paragon will pick up a standardised set of SCSS variables such as those
relating to colours, colour shades, font styles, and responsible breakpoints
and expose these as CSS variables with a ``--pgn`` prefix. These variables will
be exported by the Paragon package, however, they will be use variables defined
in the branding package thus allowing them to be redefined easily.

Any current usage of SCSS variables in MFEs can then be replaced with the
equivalent CSS variable.


Consequences
************

Once this is implemented, MFEs, and other consumers of Pargaon can start using
xitij2000 marked this conversation as resolved.
Show resolved Hide resolved
CSS variables, wherever they are currently using SASS variables. The
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be great to strive to theming capabilities with either CSS variables or SCSS variables depending on the needs of the consumer/application. Is the intention here to no longer rely on SCSS variables all, and if so, it's worth calling out the impacts of that on consumers in terms of necessary refactors, etc. We'd want to ensure we minimize breaking changes as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention isn't to abandon SCSS variables, those will still be there, and before style-tokens are realised, they will probably be the source of the CSS variable values. So in cases where SCSS variables are directly needed, they can be used.

However, what it's building towards is that MFEs should stop using SCSS variables, since that will allow them to be built independently of Paragon, so that theme changes can be applied to an MFE without rebuilding it. Details on that are coming in a separate ADR soon.

limitiation right now is that many of the variables needed by MFEs are only
available as SCSS variables.

Since these will be defined in the common theme, that means rebuilding it once
and deploying it to all MFEs (or to a common place shared by all MFEs) will
automatically apply all the latest variables.

After this ADR we can create a common stylesheet containing Bootstrap + Paragon
+ the Branding package that is loaded by all MFEs. The MFEs themselves should
not import this as SCSS but as compiled CSS and only use CSS variables. This
way each MFE will then have two stylesheets, a common stylesheet used by all
MFEs which also includes variable definitions, and an MFE-specific stylesheet
that uses only CSS variables. So the common stylesheet becomes a runtime
dependency rather than a build time dependency, allowing it to be built and
replaced at run time without needing to rebuild a growing number of MFEs.

Implementing this ADR will not accomplish the above, however it paves the way
for such a change. The above will be the subject of a future ADR.

`CSS Variables are well supported`_ in major browsers since many years, and
align well with the currently supported browsers.

Rejected Alternatives
*********************

1. Wherever an SCSS variable is deemed necessary, simply create a Paragon CSS
class instead. This can be worthwhile for specific cases where the styling has
broad use or can be made into a component, however there are cases where this
is less useful. For instance with one-off UI elements that only make sense in a
particular MFE (`example
<https://github.com/openedx/frontend-app-learning/blob/6257cb4b588fc4f9903113e22b318a63d1ddfe8e/src/course-home/progress-tab/course-completion/CompletionDonutChart.scss#L57-L74>`_)
2. Keep a common core theme but also rebuild each MFE when it changes. This
removes some of the benefits of a single theme file, such as a single common
theme deployment, and quicker theme compilation and deployment.
3. Run-time compilation of SASS using `sass.js`_. This is a major performance
hit and adds around 4.5MB of minified JavaScript.

References
**********

.. _Documenting Architecture Decisions: https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
.. _this document for Bootstrap 4.x: https://getbootstrap.com/docs/4.6/getting-started/theming/#css-variables
.. _Bootstrap 5.x: https://getbootstrap.com/docs/5.0/customize/css-variables/#component-variables
.. _CSS Variables are well supported: https://caniuse.com/css-variables
.. _broad support across browsers: https://caniuse.com/css-variables
.. _sass.js: https://github.com/medialize/sass.js