We track sessions with cookies For what?
close

Select a language

<p><b>Light and dark theme with CSS variables</b></p>
August 30, 2022#tech

Light and dark theme with CSS variables

Today it's impossible to imagine a modern web app (and not only web) with hundreds of thousands of users without changing light and dark themes. 

In this article we will show you why this is important and how you can implement this theme overlay in your website using native CSS variables.

Why the dark theme has become popular

There are quite a few reasons for this. We'll focus on the most significant ones:

1) At night, themes with light text on a dark background reduce the burden on the eyes - the contrast of the environment with the device screen becomes less noticeable, thus the user is comfortable to use the application. Also dark theme is used as a tool of accessibility, it is important for people with disabilities.

2) The majority of users, according to the study, prefer a dark theme for aesthetic reasons. It is also worth remembering that an important condition for the introduction of a dark theme is a minimalistic design of the app, which does not ruin the impression when switching the theme.

3) Dark theme is also relevant for those who care about saving smartphone battery power - according to research from Android developers, a dark theme can save up to 60% of battery power.

What are CSS variables?

CSS variables (custom CSS properties) are CSS author-defined entities that store specific values that can be reused in a document. They are set using custom property notation (e.g. --main-color: black;) and are available through the var() function (e.g. color: var(--main-color);) .

You can read more about it here.

Deploying

Initialize the project

To keep things simple we'll use the node-sass package with React 18.

To do this we initialize the project with npm and use it to add the node-sass package for easy development:

npx create-react-apptheme npm i node-sass

After initializing the project, clean up the file structure a bit, leaving only App.js and index.js in the src folder.

Creating a styles file

Make a separate folder called styles where we create a file themes.scss.

Specify our styles in it in the :root pseudo-element so that all app components can use our variable:

// src/styles/themes.scss

:root {

--text-color: #121212;

--background: #FFFFFF;

}

Let's also create a styles file for our App.js to check that the styles work:

// src/App.scss

* {

  margin: 0;

 padding: 0;

}

.App {

  font-family: sans-serif;

  text-align: center;

  height: 100vh;

  color: var(--text-color);

  background-color: var(--background);

  >div {  

    padding: 16px;

}

}

Don't forget to import our styles file:

// src/index.js

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';

import '../src/styles/theme.scss'

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render( <App />, );

// src/App.js

import './App.scss';

function App() {

  return (  

    <div className="App">

      <div>

        Переключатель темы

     </div>  

   </div> );

}

export default App;

Now, if you open the developer's console, we see that instead of specific colors in HEX-format - CSS variables, when you click on it, it will take us to the value of that variable.

Switching Theme

Now we need to implement theme switching.

If the user is new to the site, we will need to take the browser's theme settings to set it as the default for our application. However, if the user has already been to the site and likes a theme other than the system theme, we need to keep those settings.

The React and localStorage hooks will help us with this task.

Let's create ThemeProvider.js in the providers folder, which will be responsible for our theme:

// src/providers/ThemeProvider.js

import React, { createContext, useEffect, useState } from 'react';

// задаем константы для наших тем

const themes = { light: 'light', dark: 'dark', };

const defineTheme = () => {

    // берем тему из хранилища браузера, если она есть в списке - возвращаем ее

    const theme = window?.localStorage?.getItem('theme');

    if (Object.values(themes).includes(theme)) return theme;

    // с помощью метода matchMedia определяем, какая из тем сейчас задана в браузере пользователя

    const media = window.matchMedia('(prefers-color-scheme: light)');

  return media.matches

  ? themes.light

  : themes.dark

// создаем контекст, с помощью которого будем передавать состояние вниз по дереву компонентов

export const ThemeContext = createContext({});

const ThemeProvider = ({ children }) => {

    // с помощью хука будем хранить данные о текущей теме

    const [ theme, setTheme ] = useState(defineTheme);

    useEffect(() => {  

        // при изменении темы устанавливаем тему в хранилище браузера и атрибут html-тэга  

        document.documentElement.dataset.theme = theme;  

        localStorage.setItem('theme', theme);

    }, [theme]);

    return (  

        <ThemeContext.Provider value={{theme, setTheme }}>

            {children}  

        </ThemeContext.Provider>

    )

};

export default ThemeProvider;

Add our provider as a wrapper over the App:

// src/index.js

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';

import ThemeProvider from "./providers/ThemeProvider";

import'../src/styles/theme.scss'

const root = ReactDOM.createRoot(document.getElementById('root')); root.render(

    <ThemeProvider>

       <App />

    </ThemeProvider>

);

We won't bother with the theme switch too much, so we'll make a regular button that encapsulates the theme switching logic.

// src/components/ThemeSwitcher/ThemeSwitcher.js

import React, { useContext } from 'react';

import { ThemeContext } from "../../providers/ThemeProvider";

const ThemeSwitcher = () => {  

    const { theme, setTheme } = useContext(ThemeContext);  

    return (    

        <button

            onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}      

            style={{padding: 12, fontWeight: 500}}    

        >      

            Сменить тему    

        </button>  

    );

};

export default ThemeSwitcher;

Add a button to the App.js component: 

// src/App.js

import ThemeSwitcher from "./components/ThemeSwitcher/ThemeSwitcher";

import './App.scss';

function App() {

    return (  

        <div className="App">    

            <div>      

                Переключатель темы

            </div>

            <ThemeSwitcher />

       </div>

   );

}

export default App;

Adding styles for a dark theme

Great! Now when we click on the button, we switch the attribute of the html tag, but so far the styles have not changed. We need to add them for a dark theme and make adjustments to the ones we already have for a light theme.

// src/styles/theme.scss

:root[data-theme='light'] {

    --text-color: #121212;

    --background: #FFFFFF;

}

:root[data-theme='dark'] {

    --text-color: #FFFFFF;

    --background: #121212;

}

That's it! That's how easy it is to plug a theme change into our app, and it scales perfectly! You can see the code here.

Conclusion

It's worth remembering that not all tools are universal and CSS variables have their disadvantages too. 

One of them is dealing with transparency. CSS variables are not digested by SCSS functions like darken or lighten. It should also be understood that if you plan to add individual styles for a component, this imposes additional complexity to the description of styles.

However, CSS variables are a powerful tool where style consistency is important. They are also useful not only for color properties, but also for indentation, fonts, and other css properties that are frequently used.