Theming our React/Bootstrap/SASS application into multiple versions

At TheNextBid we created a platform which supports a dashboard and widgets which can be embedded into external partner websites.

The problem

So far we have solely been using Bootstrap with SASS support to support only one theme (our own). Now that our platform starts to be more mature we also receive questions from our partners to somehow apply their own theme onto our widgets, or even get a different themed dashboard depending on the logged in partner. Also we have to change some input components to a more Material Design look and feel.

None of these features are urgently requested, however they could be in the near future. With this in mind we had to take some time and see how we could implement these kind of features without making a mess.

Our options

What directly came to mind was to create a “white label” version of our platform with the minimum amount of styling. Next, we would create an option to inject a stylesheet from our partners or from ourselves to customize this white label version.

The danger using only this method is that the custom stylesheet may have to override a huge amount of style attributes for specific components. We have to include this option for sure, but we will also have to provide an option to override styling variables such as the “primary color” or the “font size”.

This is something we already do for our current theme. Each of our stylesheets first includes a variables file which is basically the same as the bootstrap variables file with some custom values on top of it.

$blue: #00ADED;
$red: #EA5049;
$green: #01D116;
$gray-600: #616161;
$body-bg: #f5f5f5;
$color-bg-light: #f5f5f5;
$font-family-display: 'Varela Round', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
$table-border-width: 2px;
$table-border-color: #b6b6b6;
$box-shadow: 0 .2rem 2rem rgba(0, 0, 0, .35);
$box-shadow-sm: 0 .1rem 1rem rgba(0, 0, 0, .35);
$border-radius:               .75rem;
$border-radius-lg: .75rem;
$border-radius-sm: .5rem;
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
$navbar-padding-y: $spacer;
$navbar-padding-x: $spacer * 2;
$navbar-brand-font-size: $font-size-base * 2;
$headings-margin-bottom: $spacer;

These variables once compiled are quite static however. The most ideal result would be something with which we could dynamically change these variables on the go; like you can place a color picker inside the platform and once you change it all the rest of the application will change directly.

Fundamentally we have the following two options to do this:

CSS custom properties

This new CSS specification allows you to define reusable variables in CSS. So instead of writing this:

.foo {
color: red;
}

You can write:

:root {
--bar: red;
}
.foo {
color: var(--bar);
}

The variables can easily be changed using JavaScript. This is a solution is quite ideal for our problem.

However this solution comes with two problems. One is that we are using Bootstrap, and CSS custom properties are not being used within Bootstrap. To use these properties anyway we would have to customize Bootstrap ourselves instead of just importing it. This comes with the obvious time consuming task of rewriting Bootstrap and also not being able to receive future Bootstrap updates anymore.

The other problem with CSS custom properties is that it is new, and therefore not supported by Internet Explorer.

Therefore we decided to look into our second option.

CSS in JS

CSS in JavaScript is basically a way to write the CSS rules in a JavaScript object such as this:

{
foo: {
color: 'red'
}
}

Next you get a library which takes the code above to generate CSS like this:

.some-generated-class {
color: 'red';
}

This library then returns a reference to some-generated-class so that you can attach that class name on a HTML element. This approach offers a lot of flexibility because the notation is in JavaScript, which can be just as dynamic as you want.

This solution also comes with some problems because it is JavaScript. It means that performance could be an issue, especially if we had to calculate lots of different shades based on the primary color for example. Another potential problem would be Bootstrap. As we simply didn’t have any experience with any of these libraries we decided to give this approach a shot and try out those libraries. Maybe some of the libraries out there would be able to import Bootstrap and place dynamic variables into it!

After a small search we found the following libraries:

Radium

This is the one library which got removed from the list before even trying it. It didn’t have support for theming as compared to the other two. Maybe it is supported in some way, but it wasn’t clear from its documentation. Another reason for not choosing Radium was the way it works: it wraps the React element and then recursively mutates the virtual DOM elements. This does not only sound very CPU intensive but it’s also contradicting the immutability on which React is built.

React JSS

Based on the documentation we found we can convert our example above into the following code:

import React from 'react';
import injectSheet, {ThemeProvider} from 'react-jss'
const styles = theme => ({
foo: {
color: theme.bar,
},
});
let MyComponent = ({
classes
}) => (
<div className={classes.foo}>
Test
</div>
);
MyComponent = injectSheet(styles)(MyComponent);
const theme = {
bar: 'red',
};
const App = () => (
<ThemeProvider theme={theme}>
<MyComponent />
</ThemeProvider>
);
export default App;

This generates the following HTML:

<style data-jss="" data-meta="MyComponent, Themed, Static">
.MyComponent-foo-0-1-1 {
color: red;
}
</style>
<div class="MyComponent-foo-0-1-1">Test</div>

As you can see the class name is generated correctly. So now the challenge is to try and import Bootstrap somehow. For this we have to compile the CSS to JavaScript objects. Of course, this could be done with a certain kind of loader, but then we lose the theme variable information: which was the entire point of using CSS in JS.

Styled Components

The above example using this library:

import React from 'react';
import styled, { ThemeProvider } from 'styled-components'
const StyledComponent = styled.div`
color: ${props => props.theme.bar};
`;
const MyComponent = () => (
<StyledComponent>
Test
</StyledComponent>
);
const theme = {
bar: 'red',
};
const App = () => (
<ThemeProvider theme={theme}>
<MyComponent />
</ThemeProvider>
);
export default App;

At first this solutions is very much the same as React JSS. However, there are some core differences. First of all we do not create the div element ourselves anymore. Instead this library gives us a new component (called MyStyledComponent in this example) which contains the right class name. The output is as follows:

<style data-styled="" data-styled-version="4.1.2">
/* sc-component-id: sc-bdVaJa */
.rOCEJ{color:red;}</style>
<div class="sc-bdVaJa rOCEJ">Test</div>

In our existing code base we have a lot of these kind of elements:

<div class="btn-group btn-group-lg w-100"></div>

So the immediate question was how to add custom classes to this MyStyledComponent. As it turns out you can also provide a className prop to MyStyledComponent which will then be added to the class.

<MyStyledComponent className="something">
Test
</MyStyledComponent>
// compiles to:
<div class="something sc-bdVaJa rOCEJ">Test</div>

So now we got that out of the way we went to the key question: how well does this library work together with Bootstrap?

The answer to this question is quite simple. At the FAQs of their website they answer this question with “you can use its existing class names alongside your components.” Which essentially means that we can still not have our Bootstrap theming based on dynamic variables.

Last resort

In the end we didn’t find any solution which could simply load our existing SASS code and generate something useful with dynamic variables. So when a proper solution doesn’t exist yet you can either go the dirty, hacky way or you create a clean solution which might take a lot of time. Let’s see both options.

Cowboy solution

Bootstrap doesn’t use custom properties, but it does compile the declarations in its output:

:root {
--blue: #00aded;
--breakpoint-lg: 992px;
--breakpoint-md: 768px;
--breakpoint-sm: 576px;
--breakpoint-xl: 1200px;
--breakpoint-xs: 0;
--cyan: #17a2b8;
--danger: #ea5049;
--dark: #343a40;
--font-family-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
--font-family-sans-serif: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--gray: #616161;
--gray-dark: #343a40;
--green: #01d116;
--indigo: #6610f2;
--info: #17a2b8;
--light: #f8f9fa;
--orange: #fd7e14;
--pink: #e83e8c;
--primary: #00aded;
--purple: #6f42c1;
--red: #ea5049;
--secondary: #616161;
--success: #01d116;
--teal: #20c997;
--warning: #ffc107;
--white: #fff;
--yellow: #ffc107;
}

We could write a script which runs after each build to search these values in the resulting CSS files and replace them with the value and the custom variable. For example:

.some-element {
color: #00aded;
}
// Changes to
.some-element {
color: #00aded;
color: var(--primary);
}

The disadvantage of this approach is that we only make exactly those values dynamic. We don’t make existing different shades of the primary color dynamic for example. Also we won’t be able to make to make conditional styling based on the given configuration, something we could easily do using CSS in JS.

A neat solution

Another solution is to strip down Bootstrap to the modules which we only need. For example the grid layout is something we don’t need to change for different themes. We can use those modules in combination with a CSS in JS solution.

This solution will take significantly more time than the above solution. It means that we will have to rewrite most of the HTML and fill up the holes of this stripped down Bootstrap.

A good thing is that such a change can happen gradually, picking up one component at a time while still developing other features.

Conclusion

Based on our findings there is not a neat solution to one on one convert our current Bootstrap theme into a theme which could be changed dynamically. We can support the option to include a custom stylesheet on top of our platform, but this will leave a lot of room for error. To dynamically change the style configuration we can use CSS custom properties in a dirty way to change the main colors. However, this won’t cover 100% of the styling and it won’t be supported in Internet Explorer. We can also gradually change our system to adopt CSS in JS and partly move away from Bootstrap. The downside of this is that it will take a long time until it will be fully functional. It is also possible to use a combination of all the solutions written above.