This is already the third article on a topic that doesn’t exist. The first article was written to describe a useful and interesting feature that also produces beautiful results. Now, however, it’s time to acknowledge that theming isn’t about imposing a soulless black-and-white world or catering to personal whims; it’s a crucial step in ensuring service accessibility and maximizing conversion rates.
If the technical part of the first article focused on the client-side, and the second on the server-side, in this third article I’d like to talk about the difficult journey styles take before reaching the site, and about the companion I created to help them - a friendly guide assisting them at every step, from design to layout. I named it Themeizer, and in this final article of the trilogy, I’d like to introduce you to it, its capabilities, and tell you about how it came to be.
Before we get to that, it’s worth refreshing your memory and reviewing the key points from the previous articles.
Key Points
Part 1. Thematization. History, Reasons, Implementation [article]
- Variable names follow the kebab-case convention. For example --shared-primary-hover. It’s worth noting that this is the expected format at the code level; in Figma, folders are created for this purpose. That is, this variable will be stored at the path shared/primary/hover.
- Variable names should reflect their purpose and not contain information about specific shades. For example --button-primary (not --blue-200)
- Styling can be configured using external style sheets, CSS-in-JS, and/or CSS variables.
- In the case of CSS variables:
- 1. Classes containing color variables are created for each theme;
- 2. A default theme is selected. A class with this theme is added to the root element;
- 3. When the theme is changed, the class on the root element is updated;
- 4. All styles are written using CSS variables.
- The theme is stored in cookies or local storage. Upon the next login, the page should be rendered with the correct theme.
Part 2. New browser APIs. Theming with SSR. Choosing between SPA, SSR, and SSG [article]
- Theming can be implemented both on the client side and on the server side. Each option has its pros and cons.
- On the server, you can retrieve the user’s theme (including for new users) and render the page immediately with the correct color scheme
Additionally, it’s worth paying attention to two more CSS properties
color-scheme
This property tells the browser which color schemes can be used to render an element. It affects user interface elements (colors of fields, buttons, scrollbars, text, background, etc.). Currently, the documentation describes the following possible values:
normal
The default value, meaning that the site does not support color schemes. Browser interface elements will be styled using a light color scheme.
light
Means that the site supports a light (daytime) theme. Browser interface elements will be styled with a light color scheme.
dark
Means that the site supports a dark (night) theme. Browser interface elements will be styled with a dark color scheme.
only
The user agent may forcefully override an element’s color scheme with the user’s preferred color scheme. This property is used to prevent such overrides (support for this value is implemented only in Safari).
<custom-ident>
Any other value can also be specified as a property. All values not described above will have no effect (this logic has been added for future backward compatibility).
In the case of theming, the property must be formatted as follows:
:root {
color-scheme: light dark;
}
.light-theme-example {
color-scheme: light;
}
.dark-theme-example {
color-scheme: dark;
}
Where color-scheme: light dark; means that the page supports light and dark schemes, with a preference for the light theme.
Since the browser needs time to load and apply styles, style flickering may occur when overriding the color scheme. To prevent this situation, you can use the<meta name="color-scheme"> meta tag, which immediately informs the user agent of the supported schemes.
For example, <meta name="color-scheme" content="dark light"> indicates that the page supports dark and light themes, with a preference for the dark theme.
accent-color
This property is used to change the accent color (the primary color) for interactive elements.
Possible values include:
auto – sets the platform’s accent color (if available);
<color> – a color in any format.
The specified color automatically adjusts the contrast of other component parts of the element (text color, background, sliders, etc.). Color variations may also be generated for gradients, etc., to bring the control into compliance with the platform’s conventions regarding the use of accent colors.
The following HTML elements are currently supported:
- <input type="checkbox">
- <input type="radio">
- <input type="range">
- <progress>
You can see how this property works on a page created by Joey Arhar – accent-color.glitch.me.
Once you’ve covered the basics, you can move on to the next section—processes and prerequisites.
Steps for implementing theming
To fully customize a theme, you need to take 3 steps:
- Design;
- Layout using variables;
- Configuring the theme switching logic.
Strange as it may seem, implementing theming starts with design. This is a very important and quite extensive step, and the later new color schemes are added, the longer this step becomes. Overall, this process can be divided into two main tasks:
- Conceiving and creating new color schemes;
- Creating layouts for each scheme.
The second point may seem unnecessary, but after creating color schemes for each theme, they need to be tested. This can only be done after creating layouts for all pages of each theme. It’s a fairly labor-intensive process, but it’s the only way to ensure that the current color palette looks high-quality everywhere and at all times.
All further design changes must also be implemented in the mockups for each theme. High-quality design systems and components solve this problem perfectly, but building the entire design solely on components is difficult and, perhaps, even harmful.
It’s clear that developing and maintaining variations for each theme is a monotonous process that takes up too much precious time. As much as I love the dark theme, it’s unfair to complicate the designer’s (and subsequently the developers’) work to such a significant degree, so it would be nice to expand the core functionality and simplify the execution of such tasks.
Design and Figma
When reading about design systems, you often come across the term “design tokens.” These typically refer to colors, typography, dimensions, effects, and other values. The standard structure of design tokens is a key-value pair. In the context of Figma, these can be described as objects that define various styles.
Figma Tokens
Despite the general concept (all styles are described as objects), Figma tokens do not have a uniform structure. For example, a color object looks like this:
{
description: "",
id: "S:8c92367364cb87031fe4e21199c200a3f8c79dd9,",
key: "8c92367364cb87031fe4e21199c200a3f8c79dd9",
name: "dark/primary",
paints: [
{
blendMode: "NORMAL",
color: {r: 0.3999999761581421, g: 0.7120000123977661, b: 1},
opacity: 1,
type: "SOLID",
visible: true
}
],
remote: false,
type: "PAINT”
}
Most other objects (typography, effects, etc.) will have nothing in common with this object’s structure, except perhaps the name and identifier. To be more precise, these are not objects themselves, but references to them. Thanks to this (or because of this), changing a style in one place will update it everywhere it’s used.
Since the main goal is to make life easier for designers when implementing theming, colors are the most important tokens to focus on. Now that we understand what exactly needs to be modified, we need to figure out “How?”
Figma for Developers
To do this, let’s turn to the Figma documentation in the “Figma for developers” section. According to it, the following options are available to developers:
- REST APIs
- Plugins
- Widgets
- Integrations
Widgets and integrations are of little interest in the context of this task, but the other two options are worth considering.
REST APIs
This interface provides read access and interaction with designs in Figma. Thanks to this API, you can retrieve all design nodes and extract styles and their properties from them. Currently, the Figma APIs allow you to retrieve “files” (objects describing the entire layout’s content) and their versions, images, users, comments, and styles, as well as submit comments on files.
Despite this impressive list of capabilities, this interface does not provide complete freedom of action, and along with important information, it returns a lot of useless information (in the context of theming).
Things are much better with plugins.
Plugins
www.figma.com/plugin-docs/intro
Plugins are web applications that extend Figma’s functionality. They can read and modify node styles, such as color, position, effects, typography, etc.
Plugins are limited to the scope of the current design; that is, they can track the selection of elements, but they cannot track the removal of styles, nor can they access styles from other projects within the current organization.
After diving into the topic of extending Figma’s functionality, we can return to our original goal: to make life easier for designers and developers.
Optimizing processes during the design phase
Creating a copy of each page for every color scheme, as mentioned at the beginning of the article, is too labor-intensive a process. Therefore, it’s worth starting the optimization right here.
Several key conditions can be identified that the final solution must meet:
Key requirements:
- the ability to change an element’s color scheme;
- ease of use;
- speed and accessibility from anywhere.
The Themeizer Plugin
To ensure ease of use, the solution should not create its own API and subsequently impose it; it should merely complement existing functionality. In Figma, style folders are typically used to create color schemes.
Therefore, folder names can be considered theme names, and all styles within them - the colors of the current scheme. Plugins provide access to the values of these styles and allow them to be modified. This means that from the plugin, you can retrieve any theme and change all styles in the mockups to a different theme. Additionally, there are situations where a page needs to display designs in different color schemes (for example, to present a new concept); in such cases, you need to change styles not across the entire design, but in individual frames (such as selected ones).
This is the first task the plugin solves
However, this comes with some additional requirements. Not every folder in a design is a theme; often there are separate folders for general styles, button elements, or branded elements. Therefore, the plugin needs additional settings where you can select themes.
All themes that are not selected as light or dark are considered shared, meaning they are available to any theme.
The plugin knows all color schemes. This means it has access to the primary source of truth, and if so, this is an opportunity to make this source not just the primary one, but the only one.
Exchange with the client side
To make Figma styles the sole source of truth, you need to create a shared repository for both the design team and the client. To do this, all styles must be uploaded to the server.
As mentioned earlier, the plugin shouldn’t impose anything, so anything can serve as the server. The only rule is that the address must accept a POST request. This address must be specified in the plugin settings, and later, upon publishing, a request containing topics with the following format will be sent to this address:
{
[theme: string]: {
list: [
{
name: string,
value: string,
type: "solid" | "linear" | "radial"
}
],
type: "light" | "dark" | "shared"
}
}
Still, before uploading to the server, it’s worth reviewing all changes and making sure everything is correct. Plugin APIs for such tasks provide access to client storage (an alternative to local storage) and to storage within the current theme. Client storage is definitely not suitable, since several people are working on the project and each of them must have access to the saved data. Storage within the current theme also limits capabilities (for example, data will be lost when the project is copied or when the plugin is reinstalled).
There is another location that knows the most recently saved styles - the server to which the plugin is already capable of writing color schemes. Accordingly, changes can also be read from it. To do this, you need to specify an address in the settings where data (using the same scheme) can be retrieved via a GET request.
Additionally, special headers may be required to execute requests. A separate field - headers - has been created for them.
After that, when publishing the next version, you will be able to see all changes.
Now all theme data is stored on the server and updated as needed. The next step is to configure retrieval on the client.
Optimizing processes during the development phase
What should happen on the client:
- Retrieving styles
- Converting to CSS variables
- Embedding classes with colors into styles
Retrieving styles
To do this, you can use the same URL as for checking changes within the plugin. Accordingly, the client needs to know this URL and the headers required for the request.
As I wrote in the previous article, theming can be configured on the server side or on the client side. In the case of server-side rendering, styles are expected to always be up to date. In this case, sending a request on every request becomes a resource-intensive operation. If theming is applied on the client, then all styles must be fetched during the build phase.
Conversion
The plugin saves all names in a convenient format for creating CSS variables - kebab-case. Thanks to this, all that remains is to retrieve all the names, turn them into variables, and create a class for each theme. Additionally, you need to add color-scheme to the classes so that for dark themes, the browser displays UI elements in a dark interface.
Integration
For client-side theming, the best approach is to embed the themes during the build process. For server-side theming, embedding should occur during page rendering. In both cases, it’s important to optimize the number of requests sent and cache their results.
Themeizer was created to address these challenges.
The “themeizer” package
The package retrieves the necessary settings from environment variables, namely the server URL, headers, and revalidate. Since there can be a large number of requests, the package supports the revalidate option out of the box, which determines the frequency of request sending.
The package supports two options for embedding styles: during compilation, by replacing the meta tag with a style tag containing classes and styles; and a manual mode, used primarily for SSR.
You can read more about the package on its page - https://www.npmjs.com/package/themeizer
The “next-themeizer” package
Additionally, a package was created for Next.js, as one of Next.js’s main goals is to lower the barrier to entry. Minimal hassle, unnecessary logic, and application configuration settings.
You can add the package to your Next.js configuration as follows:
// next.config.js
const withThemeizer = require('next-themeizer').default;
module.exports = withThemeizer();
https://www.npmjs.com/package/next-themeizer
Process Optimization During Implementation
The packages listed above will work perfectly if the site is already built on CSS variables and uses them throughout. However, if the decision to add theming is made after the product has already been created, the process of transitioning to variables can take a significant amount of time.1
The “themeizer-cli” package
Another feature of the ecosystem is simplified implementation. For this, you can use the themeizer-cli package. This is a command-line utility that automatically replaces colors in style sheets with variable names from the desired theme.
Example of use:
npm install themeizer-cli -g
themeizer-cli –c ./themeizer.config.json
// or
themeizer-cli –u <https://server-url.com/themes> -t light
https://www.npmjs.com/package/themeizer-cli
The utility is just getting started and has many improvements ahead of it. One of its main drawbacks at the moment is the inability to control changes. It simply goes through all styles and changes colors wherever a variable can be used.
Change control and color support are also important components of theming that, of course, could not be overlooked.
The “stylelint-themeizer” package
When working with styles and validating them, the main tool is Stylelint (ESLint for styles), which can detect errors and automatically fix them.
Stylelint-themeizer is a plugin that adds a new rule: if this rule is enabled, styles will be automatically checked, and if a color appears in published styles but is not defined as a CSS variable, it will display an error and suggest the variable name. When used with the --fix argument, Stylelint will automatically fix all styles.
www.npmjs.com/package/stylelint-themeizer
Note: Unfortunately, Stylelint still does not have full Sass support (e.g., the existing postcss-sass parser cannot correctly process styles without the “#” prefix).
Conclusion
Themeizer has taken its first steps toward maturity, but there is still a long way to go. Perhaps bringing it into the spotlight at such an early stage is a hasty decision. But without a doubt, it is already capable of much, ready to grow and evolve, and waiting for the opportunity to help all developers make their web worlds more convenient and accessible.
Links:
- Plugin - https://www.figma.com/community/plugin/1065764293242137356/Themeizer
- themeizer - https://www.npmjs.com/package/themeizer
- next-themeizer - https://www.npmjs.com/package/next-themeizer
- themeizer-cli - https://www.npmjs.com/package/themeizer-cli
- stylelint-themeizer - https://www.npmjs.com/package/stylelint-themeizer