HomePosts

Add Light & Dark Themes to Material UI NextJs Projects

MUI
NodeJS
NextJs

Fri Dec 30 2022

Material UI is widely know for its ability to easily create customisable themes that work out of the box with MUI. This post will take advantage of said themes and will show the basics as to how you can create a Light & Dark theme which is togglable on the Client Side. 

If you prefer to watch a video that will teach you the concept then feel free check this video out

Install the Required Packages

You can skip this step if you have already installed MUI 5+ and required packages already. However, if you have not then you will need to install the following packages

npm install @mui/material @emotion/styled @emotion/react --save

Creating the Themes

To get started lets create the two themes. We will take advantage of Material UI's createTheme function where the themes will supply different modes that tells the function to use the base Light or Dark themes from MUI. 

For the case of this post we will add the following snippet inside of the _App file as this file within NextJs hooks onto Pages when they are called meaning anything that is global should be added inside of _App. 

import { ThemeProvider, createTheme } from '@mui/material/styles';

const lightTheme = createTheme({
  palette: {
    mode: 'light'
  }
})

const darkTheme = createTheme({
  palette: {
    mode: 'dark'
  }
})

You should see from the snippet we also added ThemeProvider within the named import. This is because in order to take advantage of themes within MUI you need to supply ThemeProvider with the Theme. What you need to do is wrap the Component within _App file within the ThemeProvider whilst supplying one of the Themes to ThemeProvider (We will change this later on). 

Your _App file should look something like

import { ThemeProvider, createTheme } from '@mui/material/styles';

const lightTheme = createTheme({
  palette: {
    mode: 'light'
  }
})

const darkTheme = createTheme({
  palette: {
    mode: 'dark'
  }
})

function App({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider theme={lightTheme}>
      <Component {...pageProps} />
    </ThemeProvider>
  )
}

export default App;

Update _App File to Toggle Chosen Theme 

We need to keep track of the desired theme so therefore we should make use of React's useState inside of _App to keep track of the applied theme and the desired theme. Ensure useState is imported and add the following snippet inside the App function. 

const [activeTheme, setActiveTheme] = useState(lightTheme);
const [selectedTheme, setSelectedTheme] = useState<'light' | 'dark'>('light');

Now we will need to create the logic that will check for any changes and if there are any we will update the state using setState. This can be done by taking advantage of useState with the selectedTheme as its dependency so the function is only ran if the value of selectedTheme changes. Add the following snippet inside of the App function in the _App file.

function getActiveTheme(themeMode: 'light' | 'dark') {
  return themeMode === 'light' ? lightTheme : darkTheme;
}

useEffect(() => {
   setActiveTheme(getActiveTheme(selectedTheme))
}, [selectedTheme]);

You can see that we have also created a function called getActiveTheme that checks the theme that is supplied and maps it to the theme we created earlier named light & dark Theme. If you wish to add a third theme and so on you should be able to update this function to support more than two themes. 

Create the Ability to Toggle the Themes Through a Function

We have created the basic logic to switch between the themes however we haven't created the ability to trigger this functionality. We will now create a function we can pass to the Component component that will give any pages accesss to the function. 

Create a function named "toggleTheme" and within the function create a constant named "desiredTheme" that checks the value the "selectedTheme" varaible we set up earlier and if it is light then light should be returned else a dark should be returned. 

The value of the variable should then be used with the setSelectedTheme function to eventually trigger the useEffect function. 

const toggleTheme: React.MouseEventHandler<HTMLAnchorElement> = () => {
  const desiredTheme = selectedTheme === 'light' ? 'dark' : 'light';

  setSelectedTheme(desiredTheme);
};

If you need to support multiple themes this function should be updated to ensure themes are valid. Consider using a map, it would be worth updating the two functions we have created for extensibility.

Next, the function should be supplied to the Component within the return section of the _App file as this will allow all Pages to have access to the function. Alternatively, you could look into using a Context here.

return (
  <ThemeProvider theme={activeTheme}>
    <Component {...pageProps} toggleTheme={toggleTheme} />
  </ThemeProvider>
)

Your _App file should now look similar to the following snippet.

import { useEffect, useState } from 'react';
import type { AppProps } from 'next/app'
import { ThemeProvider, createTheme } from '@mui/material/styles';

const lightTheme = createTheme({
  palette: {
    mode: 'light'
  }
})

const darkTheme = createTheme({
  palette: {
    mode: 'dark'
  }
})

function getActiveTheme(themeMode: 'light' | 'dark') {
  return themeMode === 'light' ? lightTheme : darkTheme;
}

function MyApp({ Component, pageProps }: AppProps) {
  const [activeTheme, setActiveTheme] = useState(lightTheme);
  const [selectedTheme, setSelectedTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme: React.MouseEventHandler<HTMLAnchorElement> = () => {
    const desiredTheme = selectedTheme === 'light' ? 'dark' : 'light';

    setSelectedTheme(desiredTheme);
  };

  useEffect(() => {
    setActiveTheme(getActiveTheme(selectedTheme))
  }, [selectedTheme]);

  return (
    <ThemeProvider theme={activeTheme}>
      <Component {...pageProps} toggleTheme={toggleTheme} />
    </ThemeProvider>
  )
}

export default MyApp

Add Ability to Toggle the Theme

We have created the function to the toggle between Light & Dark so now we should supply the function to something we can click so that we can test the functionality and confirm the Light & Dark switch works as expected. 

Inside of the page within NextJS you want to toggle this functionality update the props file to expect to receive a function named toggleTheme that takes "React.MouseEventHandler<HTMLButtonElement>" as the type.

type HomeProps = {
  toggleTheme?: React.MouseEventHandler<HTMLButtonElement>;
}

const Home = (props: HomeProps) => {
// Example Page, Shortened
};

Inside of Home import Button from MUI and supply the function to the onClick property of the field and you should see onced clicked the themes are switched. 

<Button
  onClick={props.toggleTheme}  
  color={'info'}
>
Toggle Theme
</Button>

Congratulations, you NextJS Material UI Project should now support multiple themes. A future post will be created regarding keeping track of the desired theme via a cookie.