Setting a Dark Mode with Material UI

Tech kitchen

by Bea Oliveira (@beaolivei)

Hey there!

I recently worked on setting up a dark mode in a project using React and Material UI and I wanted to share some of the things I learned while doing that. There are each time more users that prefer using the web in dark mode for more reading comfort and it is a feature that is slowly becoming an industry-standard such as responsiveness and others. There are different tutorials online that explain how to set a dark mode theme using React Context and I really recommend you check them out if you haven’t yet. In this post,
however, I want to focus on my experience implementing it in a real, working commercial software, outlining some problems and ways my colleagues and I found to solve them. Here I will assume that you already have basic knowledge in React, React Context, Material Design, and Material UI.

Ok. Let’s get cracking, shall we?

1. Set a ThemeProvider and a getTheme() function from the beginning of the

When I started working on adding a different view mode I was positively surprised that my colleagues had already set up a ThemeProvider and proper getTheme() function in the project when they first started using the Material UI library. That is a cool practice because it is something that can be used both for applying themes from different users/clients (if your company ever takes that road) and it can also be used for adding a dark theme. This is a good practice when working with Material UI as it allows you to manage your primaries, secondaries, information, and error colors in one unified file. Also, as far as creating legacy code goes this one guarantees positive karma on your account, for sure.

You can find more information on how to set a theme and create a ThemeProvider in Material UI’s official documentation. There are also a few videos online that are super helpful. I recommend watching this one for a nice overview (the theme stuff can be found from minute 24:07).

I think that one main thing to keep in mind when you apply this in a real-life project is that all these parts, the createTheme() function, the values from your palette, spacing, breakpoints, and so on, will be separated files. When creating your palette specifically you can follow the data structure used by MUI for their default theme, so you keep a matching structure.

2. Palette type dark magic

Okay, so you have your ThemeProvider, your function, your separate palette files all beautifully created. Now, let’s say you want to create a dark theme. The beauty of using Material UI is that you won’t necessarily have to manually set colors for dark theme. You can only add a type value ‘dark’ and boom. Magic!

So inside your dark-theme.colors.ts you will have something like this:

palette: {
type: 'dark',
primary: {
light: myColors.grayLight,
main: myColors.gray,
dark: myColors.grayDark,
secondary: {
light: myColors.whitesmoke,
main: myColors.white,
dark: myColors.snow,
info: {
light: myColors.greenLight,
dark: myColors.greenDark,

Now, this will only work if you followed the Mui default theme structure. This means that for your background you used something like theme.palette.background.default and for your text color something like theme.palette.text.primary. If you did that then you are almost there.

Honestly, in this process, the trickiest part is dealing with the primaries and secondaries for the dark theme. Normally the designs are done mixing and matching the color scheme. Sometimes the primary is the background and sometimes it is the font color. This type of setting will make the dark mode harder to manage for sure and these are the situations that you will need to spend more time figuring out how to adapt to. This is one of these situations that having good design-ops and a nicely done design system that takes into account a dark version of components will come a long way (owww ideal world, how we wish you could ever be real ❤️).

3. Managing the view mode state in the AppProvider level

I guess that this is the part that is the hardest to abstract from the tutorials, after all, you will need to find a way to change the state in the AppProvider level, in which you will pass the value theme for your ThemeProvider. However, differently from the tutorials, your button that sets the state will probably not be in the same file. You probably will keep your button on a navigation bar or some other element that is elsewhere in your app. One solution we used to make this value available at the highest level was saving it as a cookie. So, when the action button for view mode was clicked, then I would set a cookie with the right file theme name. This way we could also save the user preference for the future and next time they were on our platform they would see their last preferred view mode.

Great. The value is saved. But, if the user clicks on the button we don’t want the page to reload, right? Another important question for us then was how to
change the value passed to the Theme Provider without triggering a reload? The solution here was to actually pass a function instead of a value to the ThemeProvider.

So, in your AppProvider.tsx level you would have something like this:

interface AppProviderProps extends RandomEl {
randomData: {
viewMode?: Brand;

const AppProvider: React.FC<AppProviderProps> = ({ ...props }) => {
const { randomData, children } = props;
const [viewMode, setViewMode] = useState(userData.viewMode);

return (
<StylesProvider injectFirst>
 <MaterialThemeProvider theme={getTheme(viewMode)}>
   <ThemeProvider theme={getTheme(viewMode)}>

export { AppProvider };

This way, when the cookie value changed, we were able to trigger a state change in the AppProvider level and display a different view mode without a reload.

Of course, there are many different ways of doing this. But the idea in this article was to just bring some ideas to those who may be working on something similar.

Happy coding, everyone!