DEV Community

loading...
Cover image for Theming with React, Less and CSS variables

Theming with React, Less and CSS variables

Kailash Sankar
I'm a full stack web developer, jack of many and master of none.
・3 min read

The styles written in less are compiled to generate a CSS file, so Less variables get converted to their values in the output. To switch a theme dynamically in the browser we'll need to change the color values on the fly, this is where CSS variables come in.

CSS variables can be declared and used in less files, we can change the variable value or swap variable definition in the browser and it's as easy as changing an element's class name.

Let's set up a basic react page to understand theming. The task can be broken down in to

  • Theme Context and Wrapper
  • Header
  • Card with some text/images
  • Theme toggle button
  • light and dark theme variables
  • CSS for the above components

Create a theme context and a wrapper component to make them available to the app.

const LIGHT_THEME = 'light-theme';
const DARK_THEME = 'dark-theme';
const ThemeContext = React.createContext();

// wrapper to make theme and changeTheme available 
// down the tree
function ThemeWrapper({ children }) {
  const [theme, setTheme] = React.useState(LIGHT_THEME);

  const applyTheme = (newTheme) => {
    // TODO: apply new theme on app
    setTheme(newTheme);
  }

  return (
    <ThemeContext.Provider value={{ theme, applyTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}
Enter fullscreen mode Exit fullscreen mode

The card component

function Card() {
  const { theme } = React.useContext(ThemeContext);
  return (
    <div className="card"> Applied theme: {theme} </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Theme toggle button

function ToggleTheme() {
  const { theme, applyTheme } = React.useContext(ThemeContext);

  const altTheme = theme === LIGHT_THEME ? DARK_THEME : LIGHT_THEME;

  const toggle = () => {
    applyTheme(altTheme);
  }

  return (
    <div className="toggle-theme"> 
      <button onClick={toggle}>Go {altTheme}</button> 
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The parent App wraps children with ThemeWrapper

function App() {
  return (
    <div id="app" className="light-theme">
      <div className="header"> Theme Sandbox </div>
      <ThemeWrapper>
        <div>
         <ToggleTheme />
         <Card />
        </div>
      </ThemeWrapper>
    </div>
  );
}

// mount to html
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

HTML just needs a root element

<div id="root"></div>
Enter fullscreen mode Exit fullscreen mode

Now let's define some essential colors for our two themes. I mixed a few palettes from colorhunt to get these.

We'll define two colors each for font, background, and border - a primary and a secondary. The themes will be defined as classes and to apply a theme we just need to apply the corresponding class.

.light-theme {
  --primary: #02475e; 
  --secondary: #194350;
  --primaryBackground: #f9f3f3;
  --secondaryBackground: #d8e3e7;
  --primaryBorder: #000;
  --secondaryBorder: #333;
}
.dark-theme {
  --primary: #f9f3f3;
  --secondary:#dddddd;
  --primaryBackground: #151515;
  --secondaryBackground: #301b3f;
  --primaryBorder: #3c415c;
  --secondaryBorder: #b4a5a5;
}
Enter fullscreen mode Exit fullscreen mode

Write styles for the rest of the items using the above variables

#app {
  color: var(--primary);
  background-color: var(--primaryBackground);
  width: 100%;
  height: 100%;
  position:absolute;
}

.header {
  text-align: center;
  font-size: 1.5em;
  margin: 10px 0px 20px 0px;
}

.toggle-theme {
  position: absolute;
  right: 10px;
  top: 5px;
}

.card {
  color: var(--secondary);
  background-color: var(--secondaryBackground);
  border: 1px solid var(--secondaryBorder);
  width: 300px;
  height: 300px;
  margin: auto;
  padding: 5px;
}
Enter fullscreen mode Exit fullscreen mode

In the app component, I have specified "light-theme" as the class, so the variables defined by our light theme would be available to the components below. Changing the theme would just mean switching the class assigned to the App component. Let's add that action to ThemeWrapper

const applyTheme = (newTheme) => { 
  document.getElementById('app').className = newTheme;
  setTheme(newTheme);
}
Enter fullscreen mode Exit fullscreen mode

The output,

Now that it's working, time to prettify the page a bit. Change the button to a switch, add some icons, font and tweak the styles to get:

Theme Context is not required to do theming if the requirement is only to change the CSS variables. But a context is useful to have the theme selection available everywhere, there might be external components where you have to pass in the theme or for taking actions based on a theme(styled-components).

That's all folks :)

Discussion (0)