Thematization: History, Causes, and Implementation 

Introduction. Reasons for its emergence 

When the web was just getting started, its sole purpose was to host content (hypertext pages) so that users could access it via the World Wide Web. Back then, design wasn’t even a consideration - after all, why would scientific publications need design? Would that make them any more useful? (The first website). Times have changed, and today the World Wide Web is far from limited to scientific publications. There are blogs, services, social networks, and much, much more. Every website needs its own unique identity; it must engage and attract users. Even scientific websites are gradually realizing this, since most scientists want not only to study various aspects but also to communicate them to the public, thereby increasing their own popularity and the value of their research (for example, 15 out of 15 scientific websites on the list have undergone a redesign in the last 6 years). Ordinary people aren’t interested in a dull website with unclear content. Science is becoming more accessible, and websites are transforming into apps with user-friendly and pleasant interfaces.

Since “convenience” means something different to everyone - there is no clear definition or specific rules for creating a service that is convenient for everyone. In recent years, the concept of “thematization” has become associated with this idea. That is exactly what I want to discuss in this article.

I think this is an extremely useful improvement. Its emergence was inevitable, and it’s even strange that it became popular so late. Both businesses and developers design their products to be accessible to everyone, everywhere, and at all times. That’s why, for example, modern interfaces now feature “shades of yellow” and “shades of blue” options. However, not everyone is satisfied with these features, so services also consider usability and the well-being of their valued users.

A dark theme for nighttime use isn’t the only reason for adding theming to a website. Another important goal is service accessibility. Worldwide, there are 285 million people with total or partial vision loss; in Russia, there are 218,000 [source], and up to 2.2 billion with various visual impairments [source]. Nearly a third of children in Russia “graduate from school wearing glasses” [source]. The statistics are staggering. However, most people are not completely blind but have only minor visual impairments. These can be color-related or quality-related. While accessibility for quality-related impairments is achieved by adding support for different font sizes, for color-related impairments, adding a theme is the ideal solution.

History of Development. An Endless Journey 

Every website corresponds to a specific color scheme. First and foremost, this concept is associated with a dark theme, and that is where we should start. In the past, when the color scheme was limited to shades of black and white, implementing a dark theme simply required inverting those shades. It’s a shame that people don’t think about such things when they’re easy to implement, but only when their implementation becomes a challenge in itself. Now, with the deepening of ties to design and the growth of user demands, the color scheme of every website is becoming unique, sometimes unexpected, and truly impressive. In this regard, designers are capable of working wonders, “combining the incompatible.” Unfortunately, such designs only work within their current color scheme; inverting their shades results in a completely awful design. Therefore, when designing modern projects that include multiple themes, color palettes - light, dark, pink, etc. - are created in advance. Later, during the design process, all shades are selected from this palette.

Design is just one component of every website. During development, a website passes through the hands of many people - developers, analysts, testers, and marketers. Theming affects each of them in one way or another. And, first and foremost, of course, the developers.

Adding theming to a project can be an extremely simple task if it’s addressed during the project planning stages. Although it has only become popular in recent years, the technology itself is by no means new. This process, like many others, has been refined and actively developed year after year over the past 5–10 years. Today, it’s hard to even imagine how the pioneers did it. You had to change the classes of all elements, optimize this through color inheritance, and update almost the entire DOM. And all this during the era of a monster like IE - a source of nightmares for seasoned developers - and before the advent of ES6. Now, however, all these problems are a thing of the past for developers. Many incredibly difficult processes are gradually fading into history, leaving future generations of developers with memories of those terrible times and excellent solutions, many of which have been perfected.

JS is one of the most dynamically evolving programming languages, but it is far from the only one evolving on the web. New capabilities are being added and old problems are being resolved in technologies such as HTML and CSS. This, of course, would be impossible without updates to the browsers themselves. The development and widespread adoption of modern browsers have lifted a heavy burden from programmers’ shoulders. These technologies don’t stop there, and I’m confident that in years to come, people will look back on them the same way programmers now look back on IE. All these updates not only simplify development and make it more convenient but also add a range of new capabilities. One such capability is CSS variables, which first appeared in browsers in 2015. 2015 turned out to be a landmark year for the web in many ways - it saw historically significant JS updates, the adoption of the HTTP/2 standard, the emergence of WebAssembly, the rise of minimalism in design, and the arrival of ReactJS. These and many other innovations are aimed at speeding up websites, simplifying development, and improving the user experience.

A brief history of CSS variables:

The earliest mention of variables I was able to find dates back to 2012. In April of that year, a description of a new concept for CSS-variables - appeared in the documentation.

A very interesting method for creating and using variables was described:

:root {
var-header-color: #06c;
}
h1 { background-color: var(header-color); }

However, before this functionality appeared in browsers, a significant amount of time had to be spent on planning and debugging. Thus, support for CSS variables was first added to Firefox only in 2015. Then, in 2016, Google and Safari followed suit.

The final implementation differed from the original idea and looked like this, which is now familiar to us:

:root {
--header-color: #06c;
}
h1 {background-color: var(--header-color); }

This method of defining variables was first described in the documentation in 2014. That same year, a description of the default value—the second argument of this function - was introduced.

It is also interesting to trace the goals behind the addition of variables. Judging by the first versions of the specification, these were the ability to eliminate duplicate constants and improve opportunities for developing adaptability. In 2015, an example of using variables for internationalization appeared. There was practically no mention of theming in that distant and important year for frontend development—the trend toward theming has only emerged in the last few years.

Variables unlocked great potential not only in theming but also in design flexibility overall. Previously, to update a color - such as that of a cancel button - you had to go through all the files, find all the classes that signified cancellation, and update their colors. Now, however, the standard practice is to create a variable, and all elements associated with the cancel action use that variable directly as their color. If a designer ever decides that this button should now be scarlet instead of red, they can confidently propose it without fear of being stoned for a minor update, despite its complexity.

In parallel with the CSS specification, its pre- and post-processors have also evolved. Their development was significantly faster, as they did not need to define the specification and promote it across all browsers. One of the first pre-processors was Stylus, created way back in 2011; later, Sass and Less were created. They offer a range of advantages and capabilities because all complex functions and modifications are converted to CSS during compilation. One such capability is variables. But these are entirely different variables, more similar to JavaScript than to CSS. Combined with mixins and JavaScript, it became possible to customize themes.

It has been 10 years since the preprocessor first appeared - a massive span of time by web standards. Many changes and additions have taken place: HTML5, ES6, 7, 8, 9, 10. JavaScript has acquired a whole range of libraries, building an ecosystem of unimaginable scale around itself. Some of these libraries have become the standard of the modern web - React, Vue, and Angular - replacing the HTML developers were accustomed to with their own JS-based alternatives. JS is also replacing CSS, giving rise to such a remarkable technology as “CSS in JS,” which offers the same capabilities but in a more dynamic and familiar format (sometimes at a high cost, but that’s a whole other story). JS has taken over the web and is now setting out to conquer the entire world.

The modern world needs these features, and since that’s the case, designers need to know how to design them, and developers need to know how to implement them. As already described, there are quite a few ways to implement them. There are just as many nuances and potential issues that can arise during the development of this capability.

Design Planning 

As mentioned earlier, it’s much better if the idea of adding themes comes up at the very start of the project. You can lay the foundation right away and continue with it in mind. After all, this is clearly easier than laying the foundation after the house is built. Although, to be fair, it’s worth noting that if the house was designed as modular, with expansion and relocation in mind, then this will be possible without additional effort.

Since a theme is an interface element, designers will take on part of the planning work. Approaches to developing design systems are constantly evolving. In the past, website design was created in programs like Photoshop (though there are still some individuals who do this today, driving developers to the brink of despair). These programs had a host of drawbacks, especially in the days of slow computers and clients with grand ideas. Of course, these programs aren’t going away; they’ll continue to be used for their primary purpose - photo editing and illustration. Their role is being taken over by modern alternatives designed primarily for the web - Avocode, Zeplin, Figma, Sketch. It’s convenient when the main tools a programmer uses are specifically designed for development purposes. In such cases, the evolution of tools keeps pace with the evolution of the fields they serve. These tools are excellent proof of that. When they first appeared, you could copy CSS styles, create grids, and check margins and padding - not with rectangles or even a ruler, but simply with a mouse movement. Then variables appeared, followed by the component-based approach entering the web world, and this approach was incorporated into these tools. They keep up with trends by creating various utilities, adding toolkits, and don’t stop there - miraculously keeping pace with this machine that has accelerated to incredible speeds.

One of the main advantages of the component-based approach is reusability. The same element can be inserted anywhere and then changed all at once with a simple gesture. But this is notable not only for copying in its original form, but also with minor modifications. One such modification could be color.

Theming can extend beyond the website page. One such opportunity is the color of the status bar and tabs in some mobile browsers. It’s also worth considering color schemes for these elements.

Color Palette

When reviewing the design of a new project, you often notice a strange but very popular way of naming colors - blue200. Of course, we can thank the designer for this, as it is indeed a valid approach, albeit for different purposes. This method works well if developers use atomic CSS, which has become the most interesting and user-friendly approach for developers in recent years, though it still lags significantly behind BEM in terms of adoption [source]. However, neither this method of naming variables nor atomic CSS is suitable for websites designed with theming in mind. There are many reasons for this; one of them is that blue200 is always a light blue color, and in order for all light blue buttons to become dark blue, you would need to change the color of all buttons toblue800. A much better option would be to name the color primary-color, because this name could be either blue200 or blue800, but it would be clear to everyone involved in development that this variable represents the site’s primary color.

colors: {
body: '#ECEFF1',
antiBody: '#263238',
shared: {
primary: '#1565C0',
secondary: '#EF6C00',
error: '#C62828',
default: '#9E9E9E',
disabled: '#E0E0E0',
},
},

For text, you can use a scheme similar to the buttons (primary, secondary, default, for disabled elements), or you can use color levels:

colors: {
...
text: {
lvl1: '#263238',
lvl3: '#546E7A',
lvl5: '#78909C',
lvl7: '#B0BEC5',
lvl9: '#ECEFF1',
},
},

That is, the primary color, the color for second-level text, and so on. When switching to a dark theme, these levels are inverted, and the interface will look just as good.

Examples of variable names:

shared-primary-color,

text-lvl1-color.

Of course, this method of naming variables cannot be absolutely universal, but it (with minor adjustments) will work for most cases.

Now that we’ve covered design planning in the context of development, we can move on to the next stage.

Code design. 

As mentioned earlier, at the code level, there are three main approaches to theme design: using native variables (with or without preprocessors), using “CSS in JS,” and replacing style sheets. Each solution can ultimately be reduced to native variables in one way or another, but the problem is that IE does not support them. Below, we’ll describe two approaches to theme design: using variables in native CSS and using “CSS in JS.”

Key steps in website theming:

  1. Creating styles for each theme (colors, shadows, borders);
  2. Configuring the default theme based on the user’s device theme (for dark and light themes);
  3. Configuring the manifest and meta tags;
  4. Creating styled components;
  5. Configuring theme switching when a button is pressed;
  6. Saving the selected theme on the user's device.

The third step is universal for any theme customization option. Therefore, let’s briefly cover it first.

The manifest is a file used primarily for PWAs. However, its contents serve as an alternative to meta tags and load faster than they do. For theming, we are interested in keys such as “theme_color” and “background_color.” Meta tags with these parameters can also be added to the page’s head section.

theme_color - the site’s theme color. The specified color will be used as the tab and status bar color on Android mobile devices. Browser support for this feature is extremely limited, but these browsers account for 67% of the market.

Source: caniuse.com

background_color - the color that will be loaded before the stylesheet is loaded. Support for this attribute is even more limited than for the theme color:

Source: caniuse.com

Variables 

It’s worth starting the description of this option with support, as this is perhaps its only drawback.

Source: caniuse.com

The complete lack of support in IE, and the long-standing lack of support in popular browsers and Safari, are not critical issues, but they are noticeable, even if they mainly affect users who are unwilling to update their browsers and devices. However, IE is still in use and is even more popular than Safari (5.87% vs. 3.62% as of 2020).

Now, regarding the implementation of this method.

  1. Create dark and light classes containing theme variables.

The method for naming variables is described in the "Design Planning" section.

The theme classes should store all variables used for theming.

.theme-light {
--body-color: #ECEFF1;
--antiBody-color: #263238;
--shared-primary-color: #1565C0;
--shared-secondary-color: #EF6C00;
--shared-error-color: #C62828;
--shared-default-color: #9E9E9E;
--shared-disabled-color: #E0E0E0;
--text-lvl1-color: #263238;
--text-lvl3-color: #546E7A;
--text-lvl5-color: #78909C;
--text-lvl7-color: #B0BEC5;
--text-lvl9-color: #ECEFF1;
}

.theme-dark {
--body-color: #263238;
--antiBody-color: #ECEFF1;
--shared-primary-color: #90CAF9;
--shared-secondary-color: #FFE0B2;
--shared-error-color: #FFCDD2;
--shared-default-color: #BDBDBD;
--shared-disabled-color: #616161;
--text-lvl1-color: #ECEFF1;
--text-lvl3-color: #B0BEC5;
--text-lvl5-color: #78909C;
--text-lvl7-color: #546E7A;
--text-lvl9-color: #263238;
}

You must decide which theme will be used by default and add its class to the body tag.

  1. Configure the default class based on the user’s device theme.

If the main goal of your site’s theming is to add a dark theme, you should pay close attention to this point. Proper configuration will provide users with an extremely pleasant experience and won’t strain their eyes if they visit your site late at night in search of valuable content.

There are at least two correct approaches to solving this problem

2.1) Setting the default theme within CSS

Add a new class that is set by default: .theme-auto

Variables are added to this class based on the device’s theme using media queries:

@media (prefers-color-scheme: dark) {
body.theme-auto {
--background-color: #111;
--text-color: #f3f3f3;
}
}
@media (prefers-color-scheme: light) {
body.theme-auto {
--background-color: #f3f3f3;
--text-color: #111;
}
}

Pros of this method:

  • no scripts
  • fast execution

Cons:

  • code duplication (variables are repeated from .theme-dark and .theme-light)
  • a script is still required to determine the theme selected during the last visit

2.2) Setting the default class using JavaScript

JS has a useful feature: tracking and checking CSS rules. One such rule, as described above, is the user’s device theme.

To check the theme and add the class for the desired theme, add the following code:

if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
body.classlist.add('theme-dark')
} else {
body.classlist.add('theme-light')
}

Additionally, you can subscribe to device theme changes:

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (e.matches) {
body.classlist.remove('theme-light')
body.classlist.add('theme-dark')
}else {
body.classlist.remove('theme-dark')
body.classlist.add('theme-light')
}
});

Pros:

  • no duplication of variables

Cons:

  • To prevent theme jumps, this code must be executed at the top level (head or beginning of the body). In other words, it must be executed separately from the main bundle.
  1. Creating styled classes for elements

./button.css

.button {
color: var(--text-lvl1-color);
background: var(--shared-default-color);
...
&:disabled {
background: var(--shared-disabled-color);
}
}
.button-primary {
background: var(--shared-primary-color);
}
.button-secondary {
background: var(--shared-secondary-color)
}

./appbar.css

.appbar {
display: flex;
align-items: center;
padding: 8px 0;
color: var(--text-lvl9-color);
background-color: var(--shared-primary-color);
}
  1. Configuring class changes when clicking the theme switch button

This is probably the simplest step. You need to add a listener to the button that will:

  • remove previous classes associated with the theme:
body.classlist.remove('theme-light', 'theme-high')
  • add the class for the selected theme:
body.classlist.add('theme-dark')
  1. Saving the selected theme on the user’s device.

The theme can be saved either in cookies or in local storage. The structure will be the same in both cases: theme: 'light' | 'dark' | 'rose'

At the top level of the site, you need to add logic to retrieve the saved theme and add the appropriate class to the body tag. For example, in the case of local storage:

const savedTheme = localStorage.getItem('theme')
if (['light', 'dark', 'rose'].includes(savedTheme)) {
body.classlist.remove('theme-light', 'theme-dark', 'theme-rose')
body.classList.add(`theme-${savedTheme}`)
}

That is, if the saved theme is one of the configured themes, we remove the classes added by default and add the class with the saved theme.

CSS-in-JS 

This option is best suited for client-side applications.

As an example, we’ll look at the combination of React + styled-components + TypeScript.

  1. Creating dark and light objects containing theme variables.

The method for naming variables is described in the “Design Planning” section.

The theme objects must store all variables used for theming.

You must decide which theme will be used by default and pass the appropriate object to the Provider.

./App.tsx

import { useState } from 'react'
import { ThemeProvider } from 'styled-components'
import themes from './theme'

const App = () => {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const onChangeTheme = (newTheme: 'light' | 'dark') => {
setTheme(newTheme)
}
return (
<ThemeProvider theme={themes[theme]}>
// ...
</ThemeProvide>
)
}
  1. Configuring the default class based on the user’s device theme.

If the main goal of your site’s theming is to add a dark theme, you should pay attention to this point. Proper configuration will provide users with an extremely pleasant experience and won’t strain their eyes if they visit your site late at night in search of valuable content.

To do this, you can set the default theme at the top level of the app:

useEffect(() => {
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
onChangeTheme('dark')
}
}, [])

Additionally, you can subscribe to device theme changes:

useEffect(() => {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (e.matches) {
onChangeTheme('dark')
} else {
onChangeTheme('light')
}
})
}, [])
  1. Creating styled components

./src/components/atoms/Button/index.tsx

import type { ButtonHTMLAttributes } from 'react'
import styled from 'styled-components'

interface StyledProps extends ButtonHTMLAttributes<HTMLButtonElement> {
fullWidth?: boolean;
color?: 'primary' | 'secondary' | 'default'
}

const Button = styled.button<StyledProps>(({ fullWidth, color = 'default', theme }) => `
color: ${theme.colors.text.lvl9};
width: ${fullWidth ? '100%' : 'fit-content'};
...
&:not(:disabled) {
background: ${theme.colors.shared[color]};
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
&:disabled {
background: ${theme.colors.shared.disabled};
}
`)

export interface Props extends StyledProps {
loading?: boolean;
}

export default Button

./src/components/atoms/AppBar/index.tsx

import styled from 'styled-components'

const AppBar = styled.header(({ theme }) => `
display: flex;
align-items: center;
padding: 8px 0;
color: ${theme.colors.text.lvl9};
background-color: ${theme.colors.shared.primary};
`)

export default AppBar
  1. Configuring class changes when clicking the theme switch button

The name of the current theme is changed via the Context API or Redux/MobX

./App.tsx

import { useState } from 'react'
import { ThemeProvider } from 'styled-components'
import themes from './theme'

const App = () => {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const onChangeTheme = (newTheme: 'light' | 'dark') => {
setTheme(newTheme)
}
return (
<ThemeProvider theme={themes[theme]}>
<ThemeContext.Provider value={{ theme, onChangeTheme }}>
...
</ThemeContext.Provider>
</ThemeProvide>
)
}

./src/components/molecules/Header/index.tsx

import { useContext } from 'react'
import Grid from '../../atoms/Grid'
import Container from '../../atoms/Conrainer'
import Button from '../../atoms/Button'
import AppBar from '../../atoms/AppBar'
import ThemeContext from '../../../contexts/ThemeContext'

const Header: React.FC = () => {
const { theme, onChangeTheme } = useContext(ThemeContext)
return (
<AppBar>
<Container>
<Grid container alignItems="center" justify="space-between" gap={1}>
<h1>
Themization
</h1>
<Button color="secondary" onClick={() => onChangeTheme(theme === 'light' ? 'dark' : 'light')}>
set theme
</Button>
</Grid>
</Container>
</AppBar>
)
}

exportdefault Header

  1. Saving the selected theme on the user's device.

The theme can be saved either in cookies or in local storage. The structure will be the same in both cases: theme: 'light' | 'dark' | 'rose'

At the top level of the site, you need to add logic to retrieve the saved theme and update the current theme value. For example, in the case of local storage:

./App.tsx

...

function App() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const onChangeTheme = (newTheme: 'light' | 'dark') => {
localStorage.setItem('theme', newTheme)
setTheme(newTheme)
}
useEffect(() => {
const savedTheme = localStorage?.getItem('theme')as 'light' | 'dark' | null
if (savedTheme && Object.keys(themes).includes(savedTheme)) setTheme(savedTheme)
else if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
onChangeTheme('dark')
}
}, [])
useEffect(() => {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (e.matches) {
onChangeTheme('dark')
} else {
onChangeTheme('light')
}
})
}, [])
return (
...
)
}

Conclusion 

There are many ways to implement theming—from creating files with all styles for each theme and switching them as needed to CSS-in-JS solutions (using native CSS variables or library-based solutions). The browser API allows you to customize the service for each specific user by reading and tracking their device’s theme.

Theming is gaining momentum, with major companies one after another implementing it into their services. A well-designed dark theme improves the user experience, reduces eye strain, saves battery life, and provides the much-desired ability to customize the service to one’s preferences. There are many benefits at a low cost, especially if everything is planned in advance.

Of course, not everyone needs theming. In any case, it involves some complications, albeit minor ones. It is needed, for example, for apps and web services.

Google and Apple services, banks, social networks, editors, GitHub, and GitLab. The list goes on and on, even though this is just the beginning of the technology’s development—and what lies ahead is bigger, better, and simpler.

Part Two - New Browser APIs. Theming with SSR. Choosing Between SPA, SSR, and SSG Part Three - Themeizer - A Young Companion to Styles

Replies

No replies found