A Figma publication for design systems creators, designers, developers, and managers
Article

White-labeling: Putting the design system in users' hands

Author
Mike Fix
Mike Fix@fixitup2
Mike is an enthusiastic developer and partner at Dawn Labs in San Francisco, where he enjoys working with others over coffee

The era of design systems is booming, and with good reason. The methodologies behind design systems can simplify development tremendously. Though they are not all equal, each system improves the development process by employing one simple paradigm: constraint. Design systems add structure to what can, and more importantly, what cannot be done. Under the right conditions, constraint can greatly increase development speed.

Using constraint to speed up development

All design systems employ constraint, but how do they differ? One comparison point is how dynamically they’re consumed. For example, a basic design system could be implemented as a component kit built within another project. On the other end of the spectrum, a complex system may be dynamically consumed through something like a CMS.

For a client of ours, we were tasked with building something in between.

Building a fully customizable app

Our client’s project involved building a web application that was fully themeable — by the client and end-users, allowing them to effectively white-label the app themselves. With those requirements in mind, we knew the building blocks needed to live within the project itself. However, the theme, the instantiation of those blocks, had to be sourced dynamically so each user could have a unique, custom experience. Also, since our client was selling this application to arbitrary companies, they needed to ensure that it was possible for their users to customize every aspect of the app to fit their brand. This was even more critical because the application would be deployed on-premise, making it difficult to push updates in a pinch.

In order to combat these limitless possibilities, we knew we wanted to create a design system to keep the app consistent. However, allowing users to customize every look and feel to meet their own brand specifications meant balancing the constraints of the system with user’s needs, creating a whole new challenge on its own.

Though building a system like this is straightforward, we found that creating a simple and manageable solution is contingent on choosing the right tools for the job.

Relying on styled components and systems

For our client, it was crucial that their customers be able to make a few changes to a theme (changing the colors and font to match their company’s brand, for example) and end up with a beautiful result. We immediately thought of two tools: styled-components and styled-system. Styled-components was the perfect tool for this job, since it includes built-in theming capabilities, and remains flexible by supporting all of CSS. It also ensures that your styles are encapsulated by components, which is ideal for building atomized units.

import { theme } from './styled' // our design system folder

export default function App(props) {
  return (
    <ThemeProvider theme={theme}>
      {props.children}
    </ThemeProvider>
  ) 
}
import styled from 'styled-components'


/* 
 * Basic themeable component:
 *
 *   → Customers are able to adjust link color
 *     for this section
 */
export const Container = styled.div`
  p {
    margin: 0;
  }

  a {
    color: ${props => props.theme.colors.primary};
  }
`

So styled-components covered flexible theming; however, in order to ensure that the app would always look great by default, we also had to introduce constraints into the system. This is where styled-system came into play. Since we were not the owners of the codebase, setting up restrictions was important to prevent future developers (or even our future selves) from using the pieces incorrectly or ballooning their individual capabilities until they are unmaintainable. Styled-system allowed us to create building-block components that only permit specific properties to be configured, providing the exact limitations we needed. To show what I mean, here’s an example of our “Header” system component:

// No need to import `react` or `styled-components`!
import system from 'system-components'

export const Heading = system(
  {
    is: 'h1',
    // primary, medium, text, etc. are all theme variables!
    fontFamily: 'primary',
    fontSize: 5,
    fontWeight: 'medium',
    lineHeight: 'normal',
    m: 0,
    color: 'text'
  },
  // only allow a few props to be used
  'fontFamily',
  'textAlign'
)

Here, the component sets a few default properties like color and font-size using the theme itself as reference, and then permits the adjustment of font-family and text-alignment. This was essential since we knew this component was going to be customized with branded fonts by the end-users. System components enabled us to support this customization while preventing unwanted changes by only exposing the properties we wanted to allow them to adjust. As a final example, take a look at how easy it is to translate a real-world style guide, like Airbnb’s, directly into a system-components theme:

dls-foundation
Source: AirBnB Design
// You can adjust these according to your use case
export const breakpoints = ['32em', '48em', '64em', '80em']

export const colors = {
  rausch: '#FF5A5F',
  babu: '#00A699',
  arches: '#FC642D',
  hof: '#4848484',
  foggy: '#767676',

  primary: '#FF5A5F'
  // more colors would go here
}

export const space = [0, 8, 16, 24, 48, 64 /* add more to your liking */]
space.tiny = space[1]
space.small = space[2]
space.base = space[3]
space.large = space[4]
space.xlarge = space[5]

export const fontSizes = [
  '8px', 
  '14px', 
  '17px', 
  '19px', 
  '24px', 
  '32px', 
  '44px'
]

export const lineHeights = [
  '8px',
  '18px',
  '22px',
  '24px',
  '28px',
  '36px',
  '56px'
]

// other things like fontWeights, borders, and shadows can be added here

Together, these two modules make it super simple to create a component kit to match any style guide.

Creating your own white label

The examples above are missing one crucial piece: where do the user’s customizations come from? We knew we needed some sort of backend to store these configurations. We also wanted to make sure that they were conventional, meaning they follow some sort of schema, in order to ensure that bad data didn’t break the application. Leaning on these requirements, we found that a GraphQL backend was perfect for the job. GraphQL forces your data to adhere to a predefined schema, and also adds the structure to enforce that only valid data is used. Like we mentioned before, these self-imposed constraints actually improve product development. By setting up a GraphQL backend, we were now able to store each user’s settings, creating a custom experience for each visitor to the app. GraphQL wasn’t a requirement, but utilizing it made setting up this system a lot easier. We used Apollo’s GraphQL client to handle most of the dirty work, and simply queried for the user’s theme, passing it directly into the hands of styled-components. Here is a stripped-down example:

import { merge } from 'lodash'
import { ThemeProvider } from 'styled-components'

import { theme } from './styled' // our design system folder

// This component wraps the Query component from `apollo-client`
import { GetTheme } from './containers'

export default function App(props) {
    return (
        <GetTheme>
            {userTheme => (
                <ThemeProvider theme={merge(theme, userTheme)}>
                    {props.children}
                </ThemeProvider>
            )}
        </GetTheme>
    ) 
}

Reintroducing flexibility

Supporting themeable colors, fonts, and spacing went a long way toward creating a white-label-able application. However, to meet our client’s needs, we needed to extend this customization further.

What they needed was a styling escape hatch. A solution that would let the end-user customize whatever they wanted to without our client’s intervention. After investigating our options, we found that this functionality called for global CSS injection. Thankfully, with styled-components V4, it could not have been simpler — it was as easy as adding a single new component to the application:

import { ThemeProvider, createGlobalStyle, css } from 'styled-components'

export const CustomStyles = createGlobalStyle`${props =>
  css`
    ${props.children};
  `}`;

// Usage
<ThemeProvider theme={theme}>
  {props.children}
  <CustomStyles>
    {/* user's custom styled go here */}
    {userTheme.customStyles}
  </CustomStyles>
</ThemeProvider>

⚠️ Note ⚠️: this specific example opens the door to XSS attacks. This was not a concern for our specific project, as it would only be deployed on-premise, but if you were to adopt a similar method you should definitely sanitize all inputs first.

You might be thinking, won’t this break the very design system we set out to create? Not if you use CSS variables. CSS variables give you a way to hook your stylesheets back into the system, keeping all your color, spacing, and font information accessible. This means your entire theme still has a single source of truth, even while supporting full CSS.

import {createGlobalStyle} from 'styled-components';

// This becomes the source of truth for the application
export default const GlobalStyle = createGlobalStyle`
  :root {
    --primary: cyan;
    --text: #506784;
    --borders: #EBF0F8;
    --page-background: transparent;
    --nav-background: white;
    /* add more colors */

    --font-primary: "Helvetica Neue", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
    --font-mono: "SFMono-Regular",  Consolas,  "Liberation Mono",  Menlo,  Courier,  monospace;

    --x1: 4px;
    --x2: 8px;
    --x3: 16px;
    --x4: 24px;
    --x5: 32px;
    --x6: 64px;

    --f1: 0.75rem;
    --f2: 0.875rem;
    --f3: 1rem;
    --f4: 1.25rem;
    --f5: 1.5rem;
    --f6: 2rem;

    /* add more variables to fill our your system */
  }
`
export const space = [
  'var(--x1)',
  'var(--x2)',
  'var(--x3)',
  'var(--x4)',
  'var(--x5)',
  'var(--x6)',
];

export const fonts = {
  primary: 'var(--font-primary)',
  mono: 'var(--font-mono)',
};

export const fontSizes = [
  'var(--f1)',
  'var(--f2)',
  'var(--f3)',
  'var(--f4)',
  'var(--f5)',
  'var(--f6)',
];

export const colors = {
  primary: 'var(--primary)',
  text: 'var(--text)',
  borders: 'var(--borders)',
  pageBackground: 'var(--page-background)',
  navBackground: 'var(--nav-background)',
  // add more colors here
};

// rest of theme:

With these components in place, the end-users can utilize the declared CSS variables to customize their app, allowing them to follow the design system themselves!

No matter how flexible a design system is, the fundamentals remain the same. Introducing constraints will not only ensure that your app doesn’t break on an edge case, but it will also drive creativity. Following these approaches to instantiate a design system can enable new features that are not really feasible without one — like custom white labelling — all without eliminating the powerful benefits of consistency and reusability that the system provided in the first place.

Further Reading

Design + Development

Design + Development

Exploring collaborations in code
Mike Fix

White-labeling: Putting the design system in users' hands

Using 'styled-components` and `styled-system` to support customization within a 3rd party application while also preventing unwanted changes.

Ryan Seddon

React containers, some assembly required

Introducing react-containers: a new open source library to help elevate no-UI containers, with a focus on accessibility, brought to you by the Zendesk Design Systems team.

John Choura

Stack mirroring: Designing for code and coding for design

What modern design systems can learn by taking direction from how programming already functions.

Read all articles

Ready to Contribute?

The team at designsystems.com is always looking for interesting content. Share your ideas with us — we’d love to hear from you.

Subscribe for Updates

We will be rolling out new articles and guides on a regular basis. Sign up to get the latest delivered to you.

Design Systems

version 2.0.0