How to build a user-customizable design system with CSS modules
Tags: css, designsystems, react, cssinjs
Published: 2022-01-25 22:43:08

At [Bonfire](https://www.getbonfire.xyz), we’re building infrastructure to enable creators to host their communities on their own branded platforms, powered by their web3 assets. We give creators an easy-to-use drag and drop interface while also allowing their personality to shine through. A challenge we faced was building a design system that allowed creators to customize their page with their own colors, background image, and dark or light mode. This blog post describes our approach and the technical tradeoffs we made. 

### **The Tech Stack**

We’re using React, Nextjs, and CSS Modules. [React](https://reactjs.org/) is a frontend framework great for building dynamic and stateful UI. [Nextjs](https://nextjs.org/) enables server-side rendering out of the box, which leads to faster and more performant web pages. [CSS modules](https://github.com/css-modules/css-modules) allow styles to be scoped to the component they apply to. This is important as a codebase scales and class name collisions are more likely. You can read more about the challenges of CSS at scale [here](https://medium.com/@tomcornilliac/css-modules-solving-the-challenges-of-css-at-scale-85789980b04f).

### Motivation

We wanted the ability to serve creator-specific CSS styles for each page. As a result, we couldn’t simply hardcode the styles in the applications. Performance was also an important factor so we wanted to avoid css-in-js frameworks like [styled-components](https://styled-components.com/), which increase bundle size. More info on the impact of css-in-js on performance [here](https://pustelto.com/blog/css-vs-css-in-js-perf/). 

### **The Solution**

The approach we landed on was to use [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), which are supported natively by all modern browsers. CSS variables are defined globally with the custom property notation (e.g. `--background: blue`) and accessed using the `var()` function (e.g. `background-color: var(--background`).

We started out by ensuring that all colors were using CSS variables instead of hardcoded hex values. These variable names will vary depending on the specific use case but we decided to prefix creator-specific variables with `--creator`. For example, we allow creators to pick a primary accent color for buttons (`--creator-primary`) as well as the text color (`--creator-on-primary`). 


![Creator-specific CSS variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s8gzcnfpou155wk9np0.png)

To support dark vs. light mode, we created new CSS variables prefaced with `--mode`. Creators are able to toggle between “dark” or “light” mode. In dark mode for example, the background color (`--mode-background`) is black while the text (`--mode-on-background`) is white. We would flip these values in light mode so that the background is white and the text is black.

Here’s our store item component in light mode and dark mode.


![Light Mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tz16k5qpszcuouonlid.png)

![Dark Mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4oy1malcwx5k0wak0lj6.png)

Another challenge we faced was dealing with grays for dark and light modes. A gray color that looked great for dark mode would have no contrast in light mode. The approach we took was to use a CSS variable grayscale that allows us to control the level of contrast regardless of whether the page is in light or dark mode. For example, a gray with value of 900 would have the most contrast with the background whereas a gray with value 50 would have the least.


![Design system gray scale](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rpilgv06pzr0j6vj3t1d.png)

The big advantage to the CSS variable approach is that developers don’t need to worry about writing conditional logic for dark vs. light. All they need to do is pick the correct CSS variable when building the component. This is a snippet of the CSS used to create the store item component above.

![Building components with the design system](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1vmrq22p1vv7g3yrzmv.png)

Putting it all together, when a creator customizes their page, we run a script to generate a new CSS stylesheet and store the file in a file store (e.g. [AWS S3](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started-nodejs.html)).

On the client side, we preload and fetch the creator-specific stylesheets, which get cached by the browser. The snippet below shows the few lines of code needed to do this. The first `link` tag with [`rel=”preload”`](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload) tells the browser to the load the content as soon as possible in the page lifecycle. Note that we’re using [nextjs/head](https://nextjs.org/docs/api-reference/next/head), which will automatically move this code to the [`head`](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML) of the document. 

![Fetching the custom stylesheets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukrbcqo8un0n6n2oxxkh.png)

### **The Result**

Here are few examples of our creator pages!


![Creator home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mbuusu39fc8vgm6t236.png)

![Creator home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fk7a2cbcd479waqabmz3.png)

![Creator home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjovv0heq7uek373z0i7.png)

### **Disadvantages**

**Lack of type-safety: **Unlike css-in-js frameworks, it’s not possible to create types for css variables and guard against deprecated or misspelled variables.

**Malformed css stylesheets:** We are programmatically generating the stylesheet, so we need to ensure the code is properly tested and CSS is sanitized.

### **Advantages**

**Super fast:** Using CSS modules doesn’t require installing additional libraries that weigh down the initial bundle size (some add more than 900kb!), which leads to the site taking longer to load.

**Developer experience:** Developers can build product knowing that the page will look good regardless of the combination of colors chosen by the creator.

Thanks for reading! If you’re interested in solving interesting problems like these, check out our [job board](https://getbonfire.notion.site/Bonfire-Job-Board-202de232b5c34200ad1252b0181f8749) and don’t hesitate to reach out.

[Link to original post](https://medium.com/bonfire-engineering-blog/how-to-build-a-user-customizable-design-system-with-css-modules-1dba7cac1636)