Recently I've built a little library, called themed-jss
for theming my web apps. It enables straightforward and flexible theming, and more importantly it automatically takes care of all additional CSS rules (and JS) needed for proper dark mode support.
In this post I will explain the problem I was trying to solve and then the solution (i.e., the lib, themed-jss
). Hope you find it useful in your web apps as well.
Theming
A theme is a set of recurring values used in your web apps style. For example, all buttons might be blue
, all text might be black
and the general background of the app might be white
:
const myTheme = {
buttons: 'blue',
text: 'black',
background: 'white'
}
It is useful to keep all of this in one place. Imagine for example that you decide to change the color of all buttons to green instead, if there is no central theme, you need to go through all of your app and update styles.
Darkmoding
Dark mode support is (or should be) just specifying an additional theme for your app (how the colors should change in dark mode):
const myTheme = {
button: 'blue',
background: 'white',
color: 'black'
}
const myDarkTheme = {
...myTheme,
background: 'black',
color: 'white'
}
Unfortunately though, in practice you need more work to add dark mode support to your web app. To get started, you need some media queries:
const myStyles = {
button: {
background: myTheme.background,
'@media (prefers-color-scheme)': {
background: myDarkTheme.background
}
}
}
☝️ I typically use JSS for styling my web apps. It provides style isolation, nested styling, etc. out of the box, and I am particularly fond of it alongside JSX (so everything is in JS).
Things become more complicated when you consider the fact that you cannot rely on system settings for dark mode as some operating systems do not have such a setting.
A solution I often use myself is adding a button that allows the user to override dark mode settings. By default, the setting is read from system (media query), but if the user overrides that, then the preference will be stored in local storage and applied as a CSS class:
const myStyles = {
button: {
background: myTheme.background,
'@media (prefers-color-scheme)': {
background: myDarkTheme.background
},
'html.--dark &': {
background: myDarkTheme.background
}
}
}
☝️ This code is actually not complete: if a user whose OS is in dark mode changes preference to light mode, this styling rule would still display in dark mode for them. To fix that, we can add a separate class indicating whether OS dark mode is being overridden or not (regardless of its value):
const myStyles = {
button: {
background: myTheme.background,
'@media (prefers-color-scheme)': {
'html:not(.--dark-mode-override) &': {
background: myDarkTheme.background
}
},
'html.--dark &': {
background: myDarkTheme.background
}
}
}
This is a lot of boilerplate for a simple style. The solution? Well if my styles where functions of a theme instead of plain values, i.e.:
const myStyles = theme => {
button: {
background: theme.background
}
}
Then I could run the styles for the two themes I have (light mode and dark mode), and insert additional CSS rules for properties that differ automatically:
const stylesInLight = myStyles(myTheme)
const stylesInDark = myStyles(myDarkTheme)
const D = diff(stylesInLight, stylesInDark)
createAndInjectRules(D)
themed-jss
What I described here is basically what themed-jss
does. It allows defining styles in terms of functions of themes, and based on that it automatically creates additional styles for dark mode.
Here is a React example:
// my-btn.style.js
import { style } from 'themed-jss'
export default style(theme => ({
btn: {
background: theme.primary,
color: theme.background,
border: 'none',
borderRadius: 3
}
})
// my-btn.jsx
import React from 'react'
import { useThemedStyle } from 'themed-jss/react'
import styles from './my-btn.style'
export default () => {
const { btn } = useThemedStyle(styles)
return (
<button className={btn}>Click ME!</button>
)
}
This component can be used like this:
// app.jsx
import { theme } from 'themed-jss'
import { Themed } from 'themed-jss/react'
import MyBtn from './my-btn'
const myTheme = theme(
{
primary: 'green',
background: 'white',
text: 'black'
},
// --> dark mode overrides:
{
background: 'black',
text: 'white'
}
)
export default () => (
<Themed theme={myTheme}>
<MyBtn/>
</Themed>
)
For manual control of dark mode, I would simply need to import DarkMode
from themed-jss/dark-mode
and initialize it:
// app.jsx
import { DarkMode } from 'themed-jss/dark-mode'
DarkMode.initialize()
// ...
And now I can have <MyBtn/>
to actually switch the dark mode:
// my-btn.jsx
import { DarkMode } from 'themed-jss/dark-mode'
// ...
export default () => {
const { btn } = useThemedStyle(styles)
return (
<button
className={btn}
onClick={() => DarkMode.toggle()}>
Click ME!
</button>
)
}
Anyways, I hope you would find themed-jss
useful as well. You can find the complete docs in the readme, and comment here (or create an issue on GitHub, etc) if you've got any further questions. This is a pretty young tool, so any feedback is much appreciated!
Top comments (0)