Dynamic Theming in Modern Javascript Applications
LinkedIn
Facebook

Within the sphere of modern applications, prioritising user experience has become paramount. As users interact with a plethora of apps offering customisation options, the ability to tailor the theme of an application is no longer just a luxury—it’s an expectation.

Implementing dynamic themes isn’t inherently complex from a technical standpoint. However, the manner in which it’s executed holds significant weight. Poor execution could potentially lead to friction between development and design teams down the line. Thus, fostering close collaboration between developers and designers is imperative. A robust theming solution should facilitate dynamic theming while being easily scalable and maintainable. To achieve this, we propose a three-layered system comprising colors, tokens, and themes.

Three-layered system:

colors.js

At the core of our theming structure lies colors.js — a constants file containing all the colors used in the app, both for light and dark themes:

				
					// colors.js
export const colors = {
  white: 'white',
  black: 'black',
  blue: {
    100: 'blue',
  },
  red: {
    100: 'red',
  },
};
				
			
tokens.js

The tokens file acts as a mapping mechanism between application elements and their corresponding colors for the themes:

				
					// tokens.js
import { colors } from './colors';
export const tokens = {
  light: {
    general: {
      appBackground: colors.white,
    },
    buttons: {
      primary: colors.blue[100],
      primaryText: colors.white,
    },
    text: {
      body: colors.black,
    },
  },
  dark: {
    general: {
      appBackground: colors.black,
    },
    buttons: {
      primary: colors.blue[100],
      primaryText: colors.white,
    },
    text: {
      body: colors.white,
    },
  },
};
				
			

Defining the tokens should be a design driven process based on the projects needs and requirements.
In this example, we create a structured map that associates colors with different elements of our application, ensuring consistency and ease of theming.

This step requires close collaboration between the development team and the design team, and it is very important for both sides to understand how and where these tokens will be used.

theme.js

The theme.js file that dynamically generates themes based on the currently active theme using the tokens previously defined:

				
					// theme.js
import { tokens } from './tokens';
export const theme = (activeTheme) => ({
  colors: {
    app: {
      background: tokens[activeTheme].general.appBackground,
    },
    buttons: {
      primary: tokens[activeTheme].buttons.primary,
      primaryText: tokens[activeTheme].buttons.primaryText,
    },
    text: {
      body: tokens[activeTheme].text.body,
    },
  },
});
				
			

With this dynamic theming structure, the theme.js file allows for effortless toggling between themes, enhancing user flexibility.

Everything comes together seamlessly in the react components, here is an example using styled-components:

				
					import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import { theme } from './theme/theme.js';
import { Header, Button } from './components/styledComponents.js';

export const Header = styled.h1`
  color: ${props => props.theme.colors.text.body};
`
Header.displayName = 'Header';

export const Button = styled.button`
  border: none;
  height: 30px;
  background-color: ${props => props.theme.colors.buttons.primary};
  color: ${props => props.theme.colors.buttons.primaryText};
`
Button.displayName = 'Button';

const ThemeExample = () => {
  const [activeTheme, setTheme] = React.useState('light');

  const switchTheme = () => { 
    if (activeTheme === 'light') {
      setTheme('dark');
      return;
    }
    setTheme('light');
  };

  return (
    <ThemeProvider theme={theme(activeTheme)}>
      <div>
        <Header>Theme Test</Header>
        <Button onClick={switchTheme}>Switch theme</Button>
      </div>
    </ThemeProvider>
  );
}

export default ThemeExample;
				
			
Conclusion: A Foundation for Flexible Theming

In summary, the modularization of color constants, abstraction through tokens, and dynamic theming provided by theme.js form a robust foundation for a flexible and consistent theming system. While the above example focuses on light and dark themes, this approach can be extended to support various other themes, ensuring adaptability to diverse user preferences and design requirements. By prioritizing collaboration and adherence to best practices, this theming structure enhances both developer efficiency and user satisfaction.

Read more tech topics