Best practices for consuming css in a web component library for multiple themes

115 Views Asked by At

We are currently developing a design system to use with multiple projects for 2 separate products.

Each product has a corresponding brand styleguide that uses design tokens, i.e:

Theme 1

primary: 'red',
secondary: 'blue',
etc...

Theme 2

primary: 'purple',
secondary: 'yellow',
etc...

The tokens are set in Figma, and then published to Zeplin, where they can be fetched using the Zeplin REST API.

For each product, we have various mobile and web applications. The mobile apps are built using React Native, and the web apps using Vue.js.

We have 2 component libraries (one for React Native components, the other for Vue.js components).

Currently, each project that consumes the component library has a build step that fetches the design tokens and uses amazon style-dictionary to generate a theme as a plain JS object, that is then passed to the component library theme provider as a prop.

We are then able to access those design variables in the design library components, and can easily switch themes by passing through a different theme object to the provider from the consuming app, e.g. for our React Native apps:

// App.jsx

import { ThemeProvider, ThemeButtonComp } from 'rn-comp-library';

<ThemeProvider theme={theme}>
    <ThemeButtonComp /> // This is styled correctly using the theme
</ThemeProvider>

This means, however, that the script to transform the design tokens has to sit in every project repository, and gets run in a build step for every single project.

We would like to set up a central repository that generates various platform specific files for both our themes into a directory, i.e:

# ./themes/theme-1/

theme-1.js // Plain JS object
theme-1.css // CSS variables

# ./themes/theme-2/

theme-2.js // Plain JS object
theme-2.css // CSS variables

We can then move the theme as a plain JS object into our React Native repositories, and pass them to the component library theme provider as we are currently doing, and that seems fine.

But ideally, for our Vue.js component library, we want to create CSS classes that uses the CSS variables generated by style dictionary, rather than passing through a plain JS object as a prop to the theme provider (which would force us to style components using inline styles). Something like:

# styles.css

.button-primary {
    background-color: var(--colors-primary);
}

What is the best way of creating this CSS file that uses the correct variables based on the active theme, and where is the best place for that file to be?

My initial thought was that we should have a variables.css located in the component library repository, containing some root variables initialised with arbitrary values, that are then set by the theme provider when a theme object is passed through, i.e:

// variables.css
:root {
  --colors-primary: white;
  --colors-secondary: white;
}

// ThemeProvider.vue
<script setup>
  document.querySelector(':root').style.setProperty('--colors-primary', props.theme.colors.primary);
</script>

// Button.vue
<style>
  .button-primary: {
     background-color: var(--colors-primary);
  }
</style>

This seems like an inefficient way of doing this, though.

Does anyone have any ideas as to the most ideal way of achieving this without manually setting CSS variables in the component library theme provider based on the theme object prop passed to the provider.

Would it be better to generate CSS variable files for each theme in our central style-dictionary repository, then either move these to the project app repositories or serve them via a CDN?

1

There are 1 best solutions below

0
On
  const bodyStyles = document.body.style
                if (this.theming.fontFamily) {
                    bodyStyles.setProperty('--font-family', `${this.theming.fontFamily}, sans-serif`)
                }

You can have a base global css file on each repository, with the default css variables that you want to update when you fetch the theme object.

The App component would be great to set the css vars programmatically on init, but be careful to wait for the theme object first, as it would break the app rendering