Why Toggle among multiple themes from one source of truth
Under the Hood
I am sure most of you have seen multiple themes like dark and white on the website.
Ever wonder how these themes are added, well nowadays, React.js supported by various third-party UI libraries provides dark/white theme toggle methods.
For example, Tailwind CSS checks the dark theme enabled in the configuration and if Yes, it opens the classname with the prefix dark to apply the dark theme to the corresponding element.
<div className="text-white dark:text-gray-900" />
For the above code, all we need is to enable the Tailwind config with a dark theme, and this is done by setting the key dark as true because it accepts a boolean value.
// tailwind.config.js file in the root directory
module.exports = () => {
darkMode: true,
};
If you are using Material UI, Mantine, or Chakra UI, the process is almost the same; we directly add the theme colour, and it will take care of under the hood.
But that’s not what we are here for. In this case, how can we add multiple themes?
Adding Multiple Themes
Well, I am a fan of scalable code and reusable code. I prefer writing automated code instead of manual if-else loops.
So I create a single source of truth called themeMap object that contains all the themes I want to add to the React.js application.
const themeMap = {
delhi: "bg-gray-50",
london: "bg-blue-50",
sydney: "bg-indigo-50",
// ....
};
This will become my theme object; you can extend this object for handling other UI components styling.
For example, if you want to add a grey shade of value 200 as the text colour, how you will do this using the themeMap
object?
This can be the DSA-related question, of how to extend this theme object to cover multiple shades of each theme.
Why I am concerned about adding other shades of each theme because we do need shades of grey, sky, indigo, or blue if the active theme is the respective one.
Extending Themes
One can use a direct theme name or colour name instead of the background colour class.
const themeMap = {
delhi: "gray",
london: "blue",
sydney: "indigo",
// .....
}
In this way, we can simply use other shades of each theme.
Another way is to extend this theme object itself.
const themeMap = {
delhi: {
current: "bg-gray-50",
next: "bg-gray-100"
},
london: {
current: "bg-blue-50",
next: "bg-blue-100"
}
}
Can you guess which Data structure is used in the above code?
Hint: Something related to a list.
The themeMap
object uses a kind of Linked List, where the node has the current value and pointer pointing to the next node.
Another good way is to use primary and secondary values just like Tailwind CSS did.
const themeMap = {
delhi: { ...colors.gray },
london: { ...colors.blue },
sydney: { ...colors.indigo }
}
This also looks good providing all kinds of colors and shades for each theme.
Toggling Theme
It’s pure Math; we will store the active theme in the useState and keep on updating the theme and setting it to the state.
const [active, setActive] = useState("jaipur");
let activeIndex = 0;
const toggleActiveTheme = (active) => {
return Object.keys(themeMap)[activeIndex + 1];
}
// haven't handled the edge case such as activeIndex <=0 and so on
In this above code, we are simply increasing the active index value by 1 from the previous value and setting the theme in the state object.
Now one can simply use the active value and map it to the themeMap object to grab the shades of colors.
const activeShades = themeMap(active);
const activeShade = themeMap(active).current;
const activeShades = themeMap(active)[50] || themeMap(active)[100];
In whatever fashion the themeMap object is designed, we can grab the active color shades from the same using the above methods.
Now the last part is to pass the object to the UI components and use the color shades.
Wow, this looks good and is easy to manage because our entire theme comes from a single source of truth which is themeMap an object.
We can further store the active theme in the cookies or local storage to avoid falling back to the default theme in the initial rendering.
Redux can also be used to store the active theme over here in the app.
Extending Color Values
This is also not tough; instead of using Tailwind CSS colour pallets, one can easily replace the colour shade with his/her values.
Simply go to the themeMap object and add different shades or values for any theme you want, for example, you can add off blue for the jaipur
theme and similar can be done for other themes.
const themeMap = {
jaipur: "#f1f1f1",
london: colors.blue,
sydney: colors.indigo
};
I prefer saying this themeMap object is highly customizable and extensible. Since it’s an object it will find all the theme shades in O(1) time saving algorithm time in calculation.
Backend in Frontend
I love this part, have you ever thought about how the admin pages or webflow, framer, and wordpress support multiple themes?
They simply store and pass the themeMap object from the backend, stored in the database.
This is not a rock-solid solution but it gives the power to change the theme automatically from a single dashboard.
I’ve talked about this a lot, how the admin page changes the entire website UI and layout and theme and that can be done using one themeMap object stored in the database.
// imagine `themeMap` object coming from the backend store in the database
// frontend fetch the themes and map them accordingly
// backend provide API to return `themeMap` object
// Admin page can use the same `themeMap` and change the active theme to any new values
Theme Toggling in Production
How Amazon and Myntra change their website theme during New Year, Diwali, and other festivals.
Most of the companies simply use the single source of truth that traverses to the bottom-most children of the DOM tree.
In this way, manually handling the theme for each child of the DOM tree in the front end is avoided.
themeMap
| |
First Parent Second Parent
| | | |
child1 child2 child3 child4
The themeMap object value is accessible by each of the children of the DOM tree.
Can you guess another data structure we are using in the above code?
It’s Tree, easy to guess but more can be learned such as Binary Tree etc.
Conclusion
While adding themes, we have covered a little bit of data structure such as
First, we structure the theme object into a kind of hash data structure
Then we use Linked List also for each theme
Lastly, we store the themeMap
object in the database so another kind of data structure is used here
In the end, we simply pass and provide access to the themeMap
object to each of the DOM tree children
Within one single example, we have and can learn so many things about DSA, objects, maps, and so on. It looks like a small project but teaches you a lot of programming stuff.
A few other concepts to learn over here are as follows
- Intersection Observer API provided by the window object
- Scroll into the view method provided by useRef of react to scroll the element in the view (I was using this in the above demo project)
- CSS animations such as
:has
operator, transition and so on.
Project Demo 👇
https://tailwind-css-theme-toggle-demo.vercel.app/
That’s it for today, see you in the next one.
Feel free to subscribe to my FREE newsletter for more such stories. I’ll write directly to your INBOX once a week.
Subscribe to my FREE newsletter
Shrey
Top comments (0)