How to add dark theme into your ReactJS or React Native app with Context and hooks

Photo by Diego Vicente on Unsplash

In this article, I’m going to cover the basic setup needed to add a theme support to your ReactJs or React Native app without using any third-party libraries. All the global state changes are going to be implemented through the usage of Context API and related hooks. No complex Redux or MobX implementation is required and the whole setup is quite straightforward.

The code of the demo app is available at GitHub. You can also download the app from Google Play or App Store.

Get your colors right

To enable multiple themes in an app the most important part is to actually define a proper primary color scheme and to create a secondary scheme without breaking the UI of your app. Try to play around with color patterns to create a similar-looking UI for all of your themes. Inverting text and background colors usually is not enough. Remember to adjust borders, disabled and error elements, etc. to have enough contrast on all of your themes.

Once the primary and secondary themes and their color schemes are chosen, add them to themes.tsx:

const themes = {
light: {
primary: '#7f00ff', // Port Gore
accent: '#ffff01', // Carrot Orange
error: '#D2473C',
success: '#40e6b3',
text: '#000000',
grey1: '#edeced',
grey2: '#c5c9cb', //inactiveLight
grey3: '#555a5e',
grey4: '#333537',
background: '#ffffff',
},
dark: {
primary: '#7f00ff', // Port Gore
accent: '#ffff01', // Carrot Orange
error: '#D2473C',
success: '#40e6b3',
text: '#ffffff',
grey1: '#edeced',
grey2: '#46494c', //inactiveLight
grey3: '#686e73',
grey4: '#c5c9cb',
background: '#000000',
},
}
export default themes

NOTE: You can reuse some colors on multiple themes to keep your brand style consistent.

Setting up the Context

Our theme setup will be controlled by the Context API, so let’s start by creating AppContext.tsx:

type State = {
themeState: 'light' | 'dark'
theme: {
primary
accent
error
success
text
grey1
grey2
grey3
grey4
background
}
}
type Action =
| {
type: 'changeThemeState'
}
export const initialState: State = {
themeState: 'light',
theme: themes.light,
}
const appReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'changeThemeState':
let themeState
let theme
Platform.OS === 'ios' &&
StatusBar.setBarStyle(state.themeState + '-content', true)
if (state.themeState === 'light') {
themeState = 'dark'
theme = themes.dark
} else {
themeState = 'light'
theme = themes.light
}
return {
...state,
themeState,
theme,
}
default:
throw new Error('Undefined action ' + action)
}
}
const StateCtx = createContext(initialState)
const DispatchCtx = createContext((() => 0) as Dispatch<Action>)

export const Provider = ({children}) => {
const [state, dispatch] = useReducer(appReducer, initialState)
return (
<DispatchCtx.Provider value={dispatch}>
<StateCtx.Provider value={state}>{children}</StateCtx.Provider>
</DispatchCtx.Provider>
)
}
export const useDispatch = () => useContext(DispatchCtx)
export const useGlobalState = () => {
return useContext(StateCtx)
}

NOTE: You can read more about the new Context API in the official docs.

As you can see from the snippet, we need a themeState variable to control our StatusBar content color and theme object to define all the color variations used in the app.

Next, we need to wrap our Context around the entire application:

const App = () => {
const state = useGlobalState()

return (
<Provider>
<StatusBar
backgroundColor={state.theme.primary}
barStyle="light-content"
/>
<DashboardScreen />
</Provider>
)
}

Optionally, you can also style your StatusBar by passing backgroundColor or barStyle from the Context we have just created.

Switching between the themes

Now, after we have the main logic covered, the only thing left is to create some functionality for changing the theme. Add a button in a convenient for the user place and pass the dispatch to trigger the desired action:

const changeThemeState = () => {
dispatch({
type: 'changeThemeState',
})
}

NOTE: I recommend to add some nice looking animated transition such as this example to provide a better UX for your users.

You can expand the action type by providing additional payload with a specific theme name if you have a multitude of themes defined. In the example, we’ll limit the functionality to a simple toggle between the light and the dark themes.

You can use the Context that we have just created for managing other global state variables. Check the demo app code to see the example of using it for a timer, persisting storage, having a sound toggle and other nice things.

You can implement as many themes as you want into your app, just always remember to keep a consistent style, contrast and accessibly in mind.

--

--

--

Cross-platform Mobile Developer and React Native fan. I like clean code, Design Systems, new tech and implementing dark themes.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Conditional rendering: React

push() and pop(), Javascript

Enhance Your Node.js Performance Through Clustering (Part 1)

Reactivating an expired domain

Saving your caption transcripts from Google Meets

A Review: Udacity’s React Nanodegree

2nd Week:Sparta Codeacdemny

Leetcode 326. Power of Three

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alex Fomushkin

Alex Fomushkin

Cross-platform Mobile Developer and React Native fan. I like clean code, Design Systems, new tech and implementing dark themes.

More from Medium

Do you want to migrate from Ionic to React Native? I can help

Ionic to React Native Roadmap

React Global State Management The Flux Way

A Quick Overview of Learning React

State management in React with redux vanilla