As light and dark themes seem to become a mandatory feature, it’s a perfect opportunity to set up theming and to showcase another important React feature: Contexts. In this post, we will explore how to set up custom themes to a Typescript React JS application using JSS.
This article assumes you already set up a React app with Typescript and JSS as your style provider. In case you need to kickstart your setup, best have a look on https://create-react-app.dev/docs/adding-typescript/. Additionally you can checkout my other post about setting up JSS Styling React Components Made Easy with JSS: A Comprehensive Guide.
First we define a ThemeMode enum that is used to define the possible values for the themeMode property in the ThemeModeContext
object. This enum includes two values, LIGHT
and DARK
, which will be used to toggle between light and dark themes.
export const ThemeMode = {
LIGHT: 'light',
DARK: 'dark',
}
Next we need a ThemeModeContext
context object that we will use to pass the current theme mode and a function to set the theme mode to our components. React contexts are used for providing a way to share data between components in a React application without having to pass props down the component tree manually. They allow for a more efficient and flexible way to manage and update shared state.
import { createContext } from 'react'
import { ThemeMode } from '../constants'
export type ThemeModeContextValue = {
themeMode: string
setThemeMode: (mode: string) => void
}
export const ThemeModeContext = createContext<ThemeModeContextValue>({
themeMode: ThemeMode.DARK,
setThemeMode: () => {},
})
Typescript is amazing but it’s just annoying to provide a proper type every time using the `theme` later in our components e.g. as the parameter of the `createUseStyles` hook. Therefore we do a little trick that makes life easier down the road. First we define the theme object globally in `global.d.ts`.
declare global {
namespace Jss {
export interface Theme {
palette: {
background: string
}
}
}
}
export {}
The theme.ts
file defines two theme objects, lightTheme
and darkTheme
, that store the color palette for each theme. These themes are defined using the DefaultTheme
interface from the react-jss
package, which we did override in the previous step. This allows us to apply to our components using the ThemeProvider
component.
import { DefaultTheme } from 'react-jss'
export const darkTheme: DefaultTheme = {
palette: {
background: '#1d1d1d',
}
}
export const lightTheme: DefaultTheme = {
palette: {
background: '#f5f5f5',
},
}
The App.tsx
file is where we bring everything together. The current theme mode is passed to the ThemeProvider
component based on theme mode, which is the value stored in localStorage
or the default value of DARK
. The ThemeModeContext.Provider
component wraps the entire application and provides access to the ThemeModeContext
context object. The handleSetThemeMode
function is used to update the themeMode
property in the context object when the user switches between themes.
import { useState } from 'react'
import { ThemeProvider } from 'react-jss'
import { lightTheme, darkTheme } from 'utils/theming/theme'
import { ThemeMode } from 'utils/theming/constants'
import { ThemeModeContext } from 'utils/theming/ThemeModeContext/Context'
import useLocalStorage from 'utils/hooks/useLocalStorage'
import MainLayout from './containers/MainLayout'
function App() {
const [initialThemeMode, setStorageThemeMode] = useLocalStorage<string>(
'themeMode',
ThemeMode.DARK
)
const [themeMode, setThemeMode] = useState(initialThemeMode)
const handleSetThemeMode = (newValue: string) => {
setThemeMode(newValue)
setStorageThemeMode(newValue)
}
return (
<ThemeModeContext.Provider
value={{ themeMode, setThemeMode: handleSetThemeMode }}
>
<ThemeProvider
theme={themeMode === ThemeMode.LIGHT ? lightTheme : darkTheme}
>
<MainLayout />
</ThemeProvider>
</ThemeModeContext.Provider>
)
}
export default App
Finally, we have the MainLayout
uses the createUseStyles
hook from the react-jss
package to define the styles for the container class. The background color for this class is defined using the palette
object from the theme
. The useStyles
hook is used to get a classes
object that is then used to apply the styles to the container div
.
import { createUseStyles } from 'react-jss'
const useStyles = createUseStyles(({ palette }) => ({
container: {
backgroundColor: palette.background
},
}))
/**
* MainLayout
*/
const MainLayout = () => {
const classes = useStyles()
return (
<div className={classes.container}>
CONTENT
</div>
)
}
export default MainLayout
To allow users to set their preferred theme mode you can provide a button on the UI of your website. Now the React context comes in handy, as a ThemeModeContext
consumer can now be used to switch between themes from within your application, no matter how deep in the component tree.
import { useContext } from 'react'
import { ThemeModeContext } from 'utils/theming/ThemeModeContext/Context'
...
const { themeMode, setThemeMode } = useContext(ThemeModeContext)
const handleClick = () => {
// toogle the theme mode
setThemeMode(
themeMode === ThemeMode.DARK ? ThemeMode.LIGHT : ThemeMode.DARK
)
}
...
<button
onClick={handleClick}
>Switch to {themeMode === ThemeMode.DARK ? 'LIGHT' : 'DARK'} mode
</button
I hope you find this article and the provided code helpful in implementing theming in your React JS application.