facinick

React JS MUI Theming tutorial

demo link: https://mui-theming.vercel.app/

code repo: https://github.com/facinick/MuiTheming

What are we doing today

  1. Create a blank react typescript application, set it up with MUI.
  2. Switch theme mode between Night and Dark (default value taken from user system, also reacts to user system theme changes).
  3. Create 3 different Themes (red, blue and green) other than default MUI theme. We will use external tool to help us create colour palette. Each theme has dark and light colour palette.
  4. Extend default theme with our own custom variables, so we can do <Button color=’google’ /> and button knows what background and text colour to use.
  5. Shuffle between these theme modes.
  6. Common ways to use the theme in our app.
  7. Checkout common mistakes to avoid that waste dev time on forums and stack.

Create a blank React Typescript application

Create a new react typescript app with create-react-app by running

npx create-react-app awesome --template typescript

in a terminal window,

NOTE: this will create an app named awesome as given in the command… you can go ahead and give it whatever name you like for ex npx create-react-app spiderman --template typescript will create an app named spiderman (reference: https://create-react-app.dev/docs/adding-typescript/ )

Great, now CD into the newly created app. (use whatever app name you gave instead of my-app)

cd ./awesome

Now open the repo in an IDE, for example if you’re using VSCode and you’ve setup it’s command in your PATH, run

code .

(notice the full-stop that tells VSCode to open project in current directory, i.e. the directory we CDed into)

your project should look similar to this

Screenshot 2022-06-01 at 3.27.21 PM.png

Go ahead and enter the following command in terminal to start the project and have our react app run in live reload mode in the browser

npm run start

or yarn

yarn start

If all worked properly, you should be seeing a Pop culture atom symbol of React!

next, we are going to install MUI packages that uses ‘styled’ as its theming engine.

Install MUI5 packages

Now we are going to install MUI 5 dependencies (reference: https://mui.com/material-ui/getting-started/installation/)

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

or yarn

yarn add @mui/material @emotion/react @emotion/styled @mui/icons-material

Done, that should be all the packages we need to build our theming demo.

next, we are going to create a theme provider (sort of a store that will have our theme to use anywhere in our app and provide us functions to change theme from anywhere in our app).

Creating the Context

Now we are going to create a Context Provider component, which will provide our various themes (red blue default green - light/dark) to our entire app. This works on react context api under the hood. More on react context api: https://reactjs.org/docs/context.html

As the react docs say: Context provides a way to pass data through the component tree without having to pass props down manually at every level.

So now once we provide our theme at the top most level, we can directly use it anywhere in our app. You’ll see more of its working later in this tutorial.

Create a file called MyThemeProvider.tsx in the following path: src/theme/MyThemeProvider.tsx

Add the following code to it:

1import React, { useEffect } from "react";
2import CssBaseline from "@mui/material/CssBaseline";
3import GlobalStyles from "@mui/material/GlobalStyles";
4import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
5import useMediaQuery from "@mui/material/useMediaQuery";
6
7export const ThemeContext = React.createContext({});
8
9type MyThemeProviderProps = {
10 children?: React.ReactNode;
11};
12
13export default function MyThemeProvider(props: MyThemeProviderProps) {
14 return (
15 <ThemeContext.Provider value={{}}> {/* We **WILL** provide functions to change theme here */}
16 <ThemeProvider theme={{}}> {/* We **WILL** provide theme here */}
17 <GlobalStyles styles={{}} />
18 <CssBaseline enableColorScheme />
19 {props.children}
20 </ThemeProvider>
21 </ThemeContext.Provider>
22 );
23}
1import React, { useEffect } from "react";
2import CssBaseline from "@mui/material/CssBaseline";
3import GlobalStyles from "@mui/material/GlobalStyles";
4import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
5import useMediaQuery from "@mui/material/useMediaQuery";
6
7export const ThemeContext = React.createContext({});
8
9type MyThemeProviderProps = {
10 children?: React.ReactNode;
11};
12
13export default function MyThemeProvider(props: MyThemeProviderProps) {
14 return (
15 <ThemeContext.Provider value={{}}> {/* We **WILL** provide functions to change theme here */}
16 <ThemeProvider theme={{}}> {/* We **WILL** provide theme here */}
17 <GlobalStyles styles={{}} />
18 <CssBaseline enableColorScheme />
19 {props.children}
20 </ThemeProvider>
21 </ThemeContext.Provider>
22 );
23}

This is our boilerplate for our Theme Context Provider

  1. We will supply our theme (that we will use everywhere) into this
  2. We will supply our functions to change theme into this

typical flow: MUI renders components with default theme → User changes theme → new theme is sent to MUI → MUI re renders it’s components with new theme

ℹ️ note: do not miss GlobalStyles and CssBaseLine components.

CssBaseline use: https://stackoverflow.com/a/59145819/17449710 [very imp]

GlobalStyles use: https://stackoverflow.com/a/69905540/17449710 [less imp]

Sure we will use our theme to provide colours to buttons and various components but we can’t change our App’s background and font color dynamically (technically that can be done but this is the MUI way). CssBaseline provides this functionality to manage background and text color of our app automatically based on what theme we provide it. (ex body html tag)

GlobalStyles just retains the page’s default css that gets reset/removed by CssBaseline. (ex body has a margin of 8px by default .. or something)

Don’t worry if you are not understanding this, once we are done building our demo, you can try remove those components to see what difference does it make to our app.

note: we still haven’t provided

  1. theme
  2. functions to change theme

to our context above, so it’s essentially useless at the moment. first we will connect this to our app and then we will add functionality to it so we don’t have to deal with connecting stuff later.

Now be a good human and head over to src/index.tsx

It must look similar to this as of now:

1import React from 'react';
2import ReactDOM from 'react-dom/client';
3import './index.css';
4import App from './App';
5import reportWebVitals from './reportWebVitals';
6
7const root = ReactDOM.createRoot(
8 document.getElementById('root') as HTMLElement
9);
10root.render(
11 <React.StrictMode>
12 <App />
13 </React.StrictMode>
14);
15
16// If you want to start measuring performance in your app, pass a function
17// to log results (for example: reportWebVitals(console.log))
18// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19reportWebVitals();
1import React from 'react';
2import ReactDOM from 'react-dom/client';
3import './index.css';
4import App from './App';
5import reportWebVitals from './reportWebVitals';
6
7const root = ReactDOM.createRoot(
8 document.getElementById('root') as HTMLElement
9);
10root.render(
11 <React.StrictMode>
12 <App />
13 </React.StrictMode>
14);
15
16// If you want to start measuring performance in your app, pass a function
17// to log results (for example: reportWebVitals(console.log))
18// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19reportWebVitals();

We are going to provide our Context store here by wrapping <App /> with ThemeProvider that we exported earlier and StyledEngineProvider from @mui/material/styles package

Below is the updated code in this file src/index.tsx, follow along the comments to know what is done:

1import React from "react";
2import ReactDOM from "react-dom/client";
3import "./index.css";
4import App from "./App";
5import reportWebVitals from "./reportWebVitals";
6// 1. Import StyledEngineProvider from MUI
7import { StyledEngineProvider } from "@mui/material/styles";
8// 2. Import ThemeProvider that we just created
9import MyThemeProvider from "./theme/MyThemeProvider";
10
11const root = ReactDOM.createRoot(
12 document.getElementById("root") as HTMLElement
13);
14
15root.render(
16 <React.StrictMode>
17 {/* 4. Wrap Theme provider with MUI Styled engine */}
18 <StyledEngineProvider injectFirst>
19 {/* 5. Wrap your app with the Theme Provider */}
20 <MyThemeProvider>
21 <App />
22 </MyThemeProvider>
23 </StyledEngineProvider>
24 </React.StrictMode>
25);
26
27// If you want to start measuring performance in your app, pass a function
28// to log results (for example: reportWebVitals(console.log))
29// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
30reportWebVitals();
1import React from "react";
2import ReactDOM from "react-dom/client";
3import "./index.css";
4import App from "./App";
5import reportWebVitals from "./reportWebVitals";
6// 1. Import StyledEngineProvider from MUI
7import { StyledEngineProvider } from "@mui/material/styles";
8// 2. Import ThemeProvider that we just created
9import MyThemeProvider from "./theme/MyThemeProvider";
10
11const root = ReactDOM.createRoot(
12 document.getElementById("root") as HTMLElement
13);
14
15root.render(
16 <React.StrictMode>
17 {/* 4. Wrap Theme provider with MUI Styled engine */}
18 <StyledEngineProvider injectFirst>
19 {/* 5. Wrap your app with the Theme Provider */}
20 <MyThemeProvider>
21 <App />
22 </MyThemeProvider>
23 </StyledEngineProvider>
24 </React.StrictMode>
25);
26
27// If you want to start measuring performance in your app, pass a function
28// to log results (for example: reportWebVitals(console.log))
29// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
30reportWebVitals();

Now that we have connected out Context provider, let’s add in our functions to change theme and also the theme itself that’s to be used by our components

Providing theme and functions to change theme

head over to src/theme/ThemeProvider.tsx

we are going to have two react states: theme (0=red,1=blue,2=green,3=default) and mode (light, dark)

Below is the updated code in this file, follow along the comments to know what is done:

1import CssBaseline from "@mui/material/CssBaseline";
2import GlobalStyles from "@mui/material/GlobalStyles";
3import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
4import useMediaQuery from "@mui/material/useMediaQuery";
5import React from "react";
6import { useEffect } from "react";
7
8// 1. create context with default values. This context will be used in places where we need to
9// change themes and modes. we will update the context vaules below to actual functions
10export const ThemeContext = React.createContext({
11 toggleColorMode: () => {},
12 shuffleColorTheme: () => {},
13});
14
15type MyThemeProviderProps = {
16 children?: React.ReactNode;
17};
18
19export default function MyThemeProvider(props: MyThemeProviderProps) {
20
21 // 2. take user theme preference using media query
22 const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
23 // 3. init state to store mode value
24 const [mode, setMode] = React.useState<"light" | "dark">(
25 prefersDarkMode ? "dark" : "light"
26 );
27 /* 4. init state to store theme value. values can be 0,1,2,3 as of now for four themes.
28 // later we will create theme presets, and export them all in an object like so
29 export const AllThemes = {
30 0: {
31 light: { blue light theme preset... },
32 dark : { blue dark theme preset... },
33 },
34 1: {
35 light: { red light theme preset...},
36 dark : { red dark theme preset...},
37 }
38 ...
39 }
40 and read them like AllThemes[theme][mode], then provide this value to MUI ThemeProvider in comment 8 below . */
41
42 const [theme, setTheme] = React.useState<0 | 1 | 2 | 3>(0);
43
44 // 5. this is to ensure, whenever user changes their system preference our code reacts to it
45 useEffect(() => {
46 setMode(prefersDarkMode ? "dark" : "light");
47 }, [prefersDarkMode]);
48
49 // 6. create a colorMode memo, memo makes sure colorMode is only initialized once (with the anonymous function)
50 // and not on every render.
51 // here is a great example of what it's useful for [https://dmitripavlutin.com/react-usememo-hook/](https://dmitripavlutin.com/react-usememo-hook/)
52 // don't worry too much about it, it's basically used for performance reasons and avoid unnecess recalculations.
53 // colorMode will be an object with keys toggleColorMode and shuffleColorTheme
54 const colorMode = React.useMemo(
55 () => ({
56 toggleColorMode: () => {
57 setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
58 },
59 shuffleColorTheme: () => {
60 setTheme((prevTheme) => ((prevTheme + 1) % 4) as 0 | 1 | 2 | 3);
61 },
62 }),
63 []
64 );
65
66 return (
67 {/*
68 7. finally, provide the colorMode constant to our theme context provider
69 later in the components where we need to call the toggleColorMode and shuffleColorTheme methods
70 we will do something like colorMode.toggleColorMode() and toggleColorMode.shuffleColorTheme()
71 */}
72 <ThemeContext.Provider value={colorMode}>
73 {/*
74 8. we still haven't provided any theme object to material ui's theme provider. this part is still pending and
75 we will do this after we have craeted various light and dark themes, combined them into an object and export
76 then import it in this file, use current value of mode and theme to pick the right theme object and supply
77 to the ThemeProvider like <ThemeProvider theme={theme}>
78 */}
79 <ThemeProvider theme={{}}>
80 <GlobalStyles styles={{}} />
81 <CssBaseline enableColorScheme />
82 {props.children}
83 </ThemeProvider>
84 </ThemeContext.Provider>
85 );
86}
1import CssBaseline from "@mui/material/CssBaseline";
2import GlobalStyles from "@mui/material/GlobalStyles";
3import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
4import useMediaQuery from "@mui/material/useMediaQuery";
5import React from "react";
6import { useEffect } from "react";
7
8// 1. create context with default values. This context will be used in places where we need to
9// change themes and modes. we will update the context vaules below to actual functions
10export const ThemeContext = React.createContext({
11 toggleColorMode: () => {},
12 shuffleColorTheme: () => {},
13});
14
15type MyThemeProviderProps = {
16 children?: React.ReactNode;
17};
18
19export default function MyThemeProvider(props: MyThemeProviderProps) {
20
21 // 2. take user theme preference using media query
22 const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
23 // 3. init state to store mode value
24 const [mode, setMode] = React.useState<"light" | "dark">(
25 prefersDarkMode ? "dark" : "light"
26 );
27 /* 4. init state to store theme value. values can be 0,1,2,3 as of now for four themes.
28 // later we will create theme presets, and export them all in an object like so
29 export const AllThemes = {
30 0: {
31 light: { blue light theme preset... },
32 dark : { blue dark theme preset... },
33 },
34 1: {
35 light: { red light theme preset...},
36 dark : { red dark theme preset...},
37 }
38 ...
39 }
40 and read them like AllThemes[theme][mode], then provide this value to MUI ThemeProvider in comment 8 below . */
41
42 const [theme, setTheme] = React.useState<0 | 1 | 2 | 3>(0);
43
44 // 5. this is to ensure, whenever user changes their system preference our code reacts to it
45 useEffect(() => {
46 setMode(prefersDarkMode ? "dark" : "light");
47 }, [prefersDarkMode]);
48
49 // 6. create a colorMode memo, memo makes sure colorMode is only initialized once (with the anonymous function)
50 // and not on every render.
51 // here is a great example of what it's useful for [https://dmitripavlutin.com/react-usememo-hook/](https://dmitripavlutin.com/react-usememo-hook/)
52 // don't worry too much about it, it's basically used for performance reasons and avoid unnecess recalculations.
53 // colorMode will be an object with keys toggleColorMode and shuffleColorTheme
54 const colorMode = React.useMemo(
55 () => ({
56 toggleColorMode: () => {
57 setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
58 },
59 shuffleColorTheme: () => {
60 setTheme((prevTheme) => ((prevTheme + 1) % 4) as 0 | 1 | 2 | 3);
61 },
62 }),
63 []
64 );
65
66 return (
67 {/*
68 7. finally, provide the colorMode constant to our theme context provider
69 later in the components where we need to call the toggleColorMode and shuffleColorTheme methods
70 we will do something like colorMode.toggleColorMode() and toggleColorMode.shuffleColorTheme()
71 */}
72 <ThemeContext.Provider value={colorMode}>
73 {/*
74 8. we still haven't provided any theme object to material ui's theme provider. this part is still pending and
75 we will do this after we have craeted various light and dark themes, combined them into an object and export
76 then import it in this file, use current value of mode and theme to pick the right theme object and supply
77 to the ThemeProvider like <ThemeProvider theme={theme}>
78 */}
79 <ThemeProvider theme={{}}>
80 <GlobalStyles styles={{}} />
81 <CssBaseline enableColorScheme />
82 {props.children}
83 </ThemeProvider>
84 </ThemeContext.Provider>
85 );
86}

Once this is done, now all that’s left is

  1. create theme objects and export them in a format so we can use theme and mode values to pick the proper theme
  2. add logic of importing theme and supplying it to MUI’s ThemeProvider

Themes

How material UI’s theming works is, there is a theme palette object that contains colours for everything. App background, text that’ll appear on app background, primary colours, text colours to use when background is of primary colours, basically everything is predetermined. From fonts to spacings to colours to everything.

Head over to https://mui.com/material-ui/customization/default-theme/ to checkout the default theme palette object and it’s values for light and dark mode. This is the default set of values you get, for example when you add color={’primary’} props to a MUI Button, this theme object tells what button background and its text is going to look like. Expand the palette key of the entire theme object. That’s what we will be tweaking to create a set of colours.

Screenshot 2022-06-01 at 7.19.03 PM.png

Now checkout theme.palette.primary key, whose value is an object with four keys namely main, light, dark, contrastText.

when you set color={’primary’} to your button, it applies the main colour to its background and contrastText to its foreground like text color. You can also tell MUI to use light or dark variants of your main colour.

Screenshot 2022-06-01 at 7.19.30 PM.png

This means if we need to create additional themes, lets say a reddish theme named Dracula… we will have to decide most of these default values (if not all) for light as well as dark modes (the above screenshot is of light mode theme palette object)

How do we decide what values to put for our Dracula theme and ensure we don’t mess up accessibility?

One fun way (not the only) that I’ll show you is as follows:

Head over to https://material-foundation.github.io/material-theme-builder/#/custom , click on Custom

Screenshot 2022-06-01 at 7.27.04 PM.png

now from the left most panel, click on Primary, select any colour you like. This will generate an entire palette to use for you on the right side.

Screenshot 2022-06-01 at 7.27.22 PM.png

Like so:

Screenshot 2022-06-01 at 7.28.08 PM.png

What does this mean? How will it help us colour our components and pages?

imagine a homepage,

It’ll take background colour of background and font colour of onBackground (from the above image)

A card on homepage will take background colour of primaryContainer and font colour of onPrimaryContainer

A button on that card will take background colour of lets say primary and text on button will take font colour of onPrimary

all of this is for light theme, for dark there is another set of colours.

now we just need to create a theme out of these values, and tell material UI when to use what.

don’t worry if things don’t make much sense, follow along:

create a file called blue.ts in src/theme/presets/blue.ts

add the following code in it and follow along the comments to know what’s done: (If you’re seeing typescript errors, don’t worry we shall get rid of them soon)

1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5// 1. we defined a new theme object which has two keys, light and dark.
6// light and dark will store palette values in a way MUI understands.
7// these palette value are picked from the obove mentioned website
8// what colour to put where? keep reading...
9export const theme = {
10 dark: {
11 palette: {
12 mode: "dark",
13 // this method augmentColor creates a MUI colour object, with other values automatically like light and dark
14 // as a colour object has main, contrastText, light and dark keys. but we need not provide light and dark keys.
15 primary: palette.augmentColor({
16 color: {
17 // pick the primary colour OF DARK and paste it here
18 main: "#cdbeff",
19 // pick the onPrimary colour OF DARK and paste it here
20 contrastText: "#32009a",
21 },
22 }),
23 secondary: palette.augmentColor({
24 color: {
25 // pick the secondary colour OF DARK and paste it here
26 main: "#cac3dc",
27 // pick the onSecondary colour OF DARK and paste it here
28 contrastText: "#322e41",
29 },
30 }),
31 text: {
32 // pick the onBackground colour OF DARK and paste it here
33 primary: "#e6e1e6",
34 // pick the onSurface colour OF DARK and paste it here
35 secondary: "#e6e1e6",
36 },
37 background: {
38 // pick the background colour OF DARK and paste it here
39 default: "#1c1b1e",
40 // pick the surface colour and OF DARK paste it here
41 paper: "#1c1b1e",
42 },
43 error: palette.augmentColor({
44 color: {
45 // pick the error colour OF DARK and paste it here
46 main: "#ffb4a9",
47 // pick the onError colour OF DARK and paste it here
48 contrastText: "#680003",
49 },
50 }),
51 success: palette.augmentColor({
52 color: {
53 // where did this come from? there is no succeess colour mentioned in thatpalette generator, but you can go ahead
54 // and add custom colours (on bottom left side of the material-theme-builder page and it'll generate palette
55 // for success for you on the right side. from there just pick success OF DARK and onSuccess OF DARK and paste here
56 main: "#79dd72",
57 contrastText: "#003a03",
58 },
59 }),
60 info: palette.augmentColor({
61 color: {
62 // same as above
63 main: "#99cbff",
64 contrastText: "#003257",
65 },
66 }),
67 warning: palette.augmentColor({
68 color: {
69 // same as above
70 main: "#cace09",
71 contrastText: "#313300",
72 },
73 }),
74 // I put the outline colour here
75 divider: "#938f99",
76 // important: these are custom variables
77 // suppose instead of doing <Button colour={'primary'} /> you want to do something like <Button colour={'upvote'} />
78 // for an upvote button? here I am creating custom variabels and supplying colours that I want based on my product design
79 upvote: palette.augmentColor({
80 color: {
81 main: "#cdbeff",
82 contrastText: "#32009a",
83 },
84 }),
85 // same as above
86 downvote: palette.augmentColor({
87 color: {
88 main: "#ffb4a9",
89 contrastText: "#680003",
90 },
91 })
92 containerPrimary: palette.augmentColor({
93 color: {
94 // pick the primary Conatiner colour OF DARK and paste it here
95 main: "#4b24ba",
96 // pick the On primary Conatiner colour OF DARK and paste it here
97 contrastText: "#e8deff",
98 },
99 }),
100 containerSecondary: palette.augmentColor({
101 color: {
102 // pick the secondary Conatiner colour OF DARK and paste it here
103 main: "#494458",
104 // pick the On secondary Conatiner colour OF DARK and paste it here
105 contrastText: "#e7dff8",
106 },
107 }),
108 },
109 },
110// REPEAT FOR LIGHT. instead of picking colours from dark palette, pick colours from the light one and repeat as above
111 light: {
112 palette: {
113 mode: "light",
114 primary: palette.augmentColor({
115 color: {
116 main: "#6342d2",
117 contrastText: "#ffffff",
118 },
119 }),
120 secondary: palette.augmentColor({
121 color: {
122 main: "#605b71",
123 contrastText: "#ffffff",
124 },
125 }),
126 text: {
127 primary: "#1c1b1e",
128 secondary: "#1c1b1e",
129 },
130 background: {
131 default: "#fffbff",
132 paper: "#fffbff",
133 },
134 error: palette.augmentColor({
135 color: {
136 main: "#ba1b1b",
137 contrastText: "#ffffff",
138 },
139 }),
140 success: palette.augmentColor({
141 color: {
142 main: "#006e10",
143 contrastText: "#ffffff",
144 },
145 }),
146 info: palette.augmentColor({
147 color: {
148 main: "#0062a2",
149 contrastText: "#ffffff",
150 },
151 }),
152 warning: palette.augmentColor({
153 color: {
154 main: "#606200",
155 contrastText: "#313300",
156 },
157 }),
158 divider: "#79757f",
159 upvote: palette.augmentColor({
160 color: {
161 main: "#6342d2",
162 contrastText: "#ffffff",
163 },
164 }),
165 downvote: palette.augmentColor({
166 color: {
167 main: "#ba1b1b",
168 contrastText: "#ffffff",
169 },
170 }),
171 containerPrimary: palette.augmentColor({
172 color: {
173 main: "#e8deff",
174 contrastText: "#1c0062",
175 },
176 }),
177 containerSecondary: palette.augmentColor({
178 color: {
179 main: "#e7dff8",
180 contrastText: "#1d192b",
181 },
182 }),
183 },
184 },
185};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5// 1. we defined a new theme object which has two keys, light and dark.
6// light and dark will store palette values in a way MUI understands.
7// these palette value are picked from the obove mentioned website
8// what colour to put where? keep reading...
9export const theme = {
10 dark: {
11 palette: {
12 mode: "dark",
13 // this method augmentColor creates a MUI colour object, with other values automatically like light and dark
14 // as a colour object has main, contrastText, light and dark keys. but we need not provide light and dark keys.
15 primary: palette.augmentColor({
16 color: {
17 // pick the primary colour OF DARK and paste it here
18 main: "#cdbeff",
19 // pick the onPrimary colour OF DARK and paste it here
20 contrastText: "#32009a",
21 },
22 }),
23 secondary: palette.augmentColor({
24 color: {
25 // pick the secondary colour OF DARK and paste it here
26 main: "#cac3dc",
27 // pick the onSecondary colour OF DARK and paste it here
28 contrastText: "#322e41",
29 },
30 }),
31 text: {
32 // pick the onBackground colour OF DARK and paste it here
33 primary: "#e6e1e6",
34 // pick the onSurface colour OF DARK and paste it here
35 secondary: "#e6e1e6",
36 },
37 background: {
38 // pick the background colour OF DARK and paste it here
39 default: "#1c1b1e",
40 // pick the surface colour and OF DARK paste it here
41 paper: "#1c1b1e",
42 },
43 error: palette.augmentColor({
44 color: {
45 // pick the error colour OF DARK and paste it here
46 main: "#ffb4a9",
47 // pick the onError colour OF DARK and paste it here
48 contrastText: "#680003",
49 },
50 }),
51 success: palette.augmentColor({
52 color: {
53 // where did this come from? there is no succeess colour mentioned in thatpalette generator, but you can go ahead
54 // and add custom colours (on bottom left side of the material-theme-builder page and it'll generate palette
55 // for success for you on the right side. from there just pick success OF DARK and onSuccess OF DARK and paste here
56 main: "#79dd72",
57 contrastText: "#003a03",
58 },
59 }),
60 info: palette.augmentColor({
61 color: {
62 // same as above
63 main: "#99cbff",
64 contrastText: "#003257",
65 },
66 }),
67 warning: palette.augmentColor({
68 color: {
69 // same as above
70 main: "#cace09",
71 contrastText: "#313300",
72 },
73 }),
74 // I put the outline colour here
75 divider: "#938f99",
76 // important: these are custom variables
77 // suppose instead of doing <Button colour={'primary'} /> you want to do something like <Button colour={'upvote'} />
78 // for an upvote button? here I am creating custom variabels and supplying colours that I want based on my product design
79 upvote: palette.augmentColor({
80 color: {
81 main: "#cdbeff",
82 contrastText: "#32009a",
83 },
84 }),
85 // same as above
86 downvote: palette.augmentColor({
87 color: {
88 main: "#ffb4a9",
89 contrastText: "#680003",
90 },
91 })
92 containerPrimary: palette.augmentColor({
93 color: {
94 // pick the primary Conatiner colour OF DARK and paste it here
95 main: "#4b24ba",
96 // pick the On primary Conatiner colour OF DARK and paste it here
97 contrastText: "#e8deff",
98 },
99 }),
100 containerSecondary: palette.augmentColor({
101 color: {
102 // pick the secondary Conatiner colour OF DARK and paste it here
103 main: "#494458",
104 // pick the On secondary Conatiner colour OF DARK and paste it here
105 contrastText: "#e7dff8",
106 },
107 }),
108 },
109 },
110// REPEAT FOR LIGHT. instead of picking colours from dark palette, pick colours from the light one and repeat as above
111 light: {
112 palette: {
113 mode: "light",
114 primary: palette.augmentColor({
115 color: {
116 main: "#6342d2",
117 contrastText: "#ffffff",
118 },
119 }),
120 secondary: palette.augmentColor({
121 color: {
122 main: "#605b71",
123 contrastText: "#ffffff",
124 },
125 }),
126 text: {
127 primary: "#1c1b1e",
128 secondary: "#1c1b1e",
129 },
130 background: {
131 default: "#fffbff",
132 paper: "#fffbff",
133 },
134 error: palette.augmentColor({
135 color: {
136 main: "#ba1b1b",
137 contrastText: "#ffffff",
138 },
139 }),
140 success: palette.augmentColor({
141 color: {
142 main: "#006e10",
143 contrastText: "#ffffff",
144 },
145 }),
146 info: palette.augmentColor({
147 color: {
148 main: "#0062a2",
149 contrastText: "#ffffff",
150 },
151 }),
152 warning: palette.augmentColor({
153 color: {
154 main: "#606200",
155 contrastText: "#313300",
156 },
157 }),
158 divider: "#79757f",
159 upvote: palette.augmentColor({
160 color: {
161 main: "#6342d2",
162 contrastText: "#ffffff",
163 },
164 }),
165 downvote: palette.augmentColor({
166 color: {
167 main: "#ba1b1b",
168 contrastText: "#ffffff",
169 },
170 }),
171 containerPrimary: palette.augmentColor({
172 color: {
173 main: "#e8deff",
174 contrastText: "#1c0062",
175 },
176 }),
177 containerSecondary: palette.augmentColor({
178 color: {
179 main: "#e7dff8",
180 contrastText: "#1d192b",
181 },
182 }),
183 },
184 },
185};

Once this is done, I want you to create 3 more similar files namely red.ts, green.ts, default,ts in the same directory as blue.ts

I’m pasting theme presets for red green and default below, just paste them as it is in your files.

1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5export const theme = {
6 dark: {
7 palette: {
8 mode: "dark",
9 primary: palette.augmentColor({
10 color: {
11 main: "#ffb3b0",
12 contrastText: "#68000c",
13 },
14 }),
15 secondary: palette.augmentColor({
16 color: {
17 main: "#4fd8eb",
18 contrastText: "#00363d",
19 },
20 }),
21 text: {
22 primary: "#E6E1E5",
23 secondary: "#E6E1E5",
24 },
25 background: {
26 default: "#1C1B1F",
27 paper: "#1C1B1F",
28 },
29 error: palette.augmentColor({
30 color: {
31 main: "#F2B8B5",
32 contrastText: "#601410",
33 },
34 }),
35 success: palette.augmentColor({
36 color: {
37 main: "#79dd72",
38 contrastText: "#003a03",
39 },
40 }),
41 info: palette.augmentColor({
42 color: {
43 main: "#99cbff",
44 contrastText: "#003257",
45 },
46 }),
47 warning: palette.augmentColor({
48 color: {
49 main: "#cace09",
50 contrastText: "#313300",
51 },
52 }),
53 divider: "#938F99",
54 upvote: palette.augmentColor({
55 color: {
56 main: "#bd0b25",
57 contrastText: "#68000c",
58 },
59 }),
60 downvote: palette.augmentColor({
61 color: {
62 main: "#4fd8eb",
63 contrastText: "#00363d",
64 },
65 }),
66 containerPrimary: palette.augmentColor({
67 color: {
68 main: "#920016",
69 contrastText: "#ffdad6",
70 },
71 }),
72 containerSecondary: palette.augmentColor({
73 color: {
74 main: "#5c3f3d",
75 contrastText: "#ffdad8",
76 },
77 }),
78 },
79 },
80 light: {
81 palette: {
82 mode: "light",
83 primary: palette.augmentColor({
84 color: {
85 main: "#bd0b25",
86 contrastText: "#ffffff",
87 },
88 }),
89 secondary: palette.augmentColor({
90 color: {
91 main: "#006874",
92 contrastText: "#ffffff",
93 },
94 }),
95 text: {
96 primary: "#1C1B1F",
97 secondary: "#1C1B1F",
98 },
99 background: {
100 default: "#FFFBFE",
101 paper: "#fffbff",
102 },
103 error: palette.augmentColor({
104 color: {
105 main: "#B3261E",
106 contrastText: "#FFFFFF",
107 },
108 }),
109 success: palette.augmentColor({
110 color: {
111 main: "#006e10",
112 contrastText: "#ffffff",
113 },
114 }),
115 info: palette.augmentColor({
116 color: {
117 main: "#0062a2",
118 contrastText: "#ffffff",
119 },
120 }),
121 warning: palette.augmentColor({
122 color: {
123 main: "#606200",
124 contrastText: "#313300",
125 },
126 }),
127 divider: "#79747E",
128 upvote: palette.augmentColor({
129 color: {
130 main: "#bd0b25",
131 contrastText: "#ffffff",
132 },
133 }),
134 downvote: palette.augmentColor({
135 color: {
136 main: "#006874",
137 contrastText: "#ffffff",
138 },
139 }),
140 containerPrimary: palette.augmentColor({
141 color: {
142 main: "#ffdad6",
143 contrastText: "#410005",
144 },
145 }),
146 containerSecondary: palette.augmentColor({
147 color: {
148 main: "#ffdad8",
149 contrastText: "#2d1514",
150 },
151 }),
152 },
153 },
154};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5export const theme = {
6 dark: {
7 palette: {
8 mode: "dark",
9 primary: palette.augmentColor({
10 color: {
11 main: "#ffb3b0",
12 contrastText: "#68000c",
13 },
14 }),
15 secondary: palette.augmentColor({
16 color: {
17 main: "#4fd8eb",
18 contrastText: "#00363d",
19 },
20 }),
21 text: {
22 primary: "#E6E1E5",
23 secondary: "#E6E1E5",
24 },
25 background: {
26 default: "#1C1B1F",
27 paper: "#1C1B1F",
28 },
29 error: palette.augmentColor({
30 color: {
31 main: "#F2B8B5",
32 contrastText: "#601410",
33 },
34 }),
35 success: palette.augmentColor({
36 color: {
37 main: "#79dd72",
38 contrastText: "#003a03",
39 },
40 }),
41 info: palette.augmentColor({
42 color: {
43 main: "#99cbff",
44 contrastText: "#003257",
45 },
46 }),
47 warning: palette.augmentColor({
48 color: {
49 main: "#cace09",
50 contrastText: "#313300",
51 },
52 }),
53 divider: "#938F99",
54 upvote: palette.augmentColor({
55 color: {
56 main: "#bd0b25",
57 contrastText: "#68000c",
58 },
59 }),
60 downvote: palette.augmentColor({
61 color: {
62 main: "#4fd8eb",
63 contrastText: "#00363d",
64 },
65 }),
66 containerPrimary: palette.augmentColor({
67 color: {
68 main: "#920016",
69 contrastText: "#ffdad6",
70 },
71 }),
72 containerSecondary: palette.augmentColor({
73 color: {
74 main: "#5c3f3d",
75 contrastText: "#ffdad8",
76 },
77 }),
78 },
79 },
80 light: {
81 palette: {
82 mode: "light",
83 primary: palette.augmentColor({
84 color: {
85 main: "#bd0b25",
86 contrastText: "#ffffff",
87 },
88 }),
89 secondary: palette.augmentColor({
90 color: {
91 main: "#006874",
92 contrastText: "#ffffff",
93 },
94 }),
95 text: {
96 primary: "#1C1B1F",
97 secondary: "#1C1B1F",
98 },
99 background: {
100 default: "#FFFBFE",
101 paper: "#fffbff",
102 },
103 error: palette.augmentColor({
104 color: {
105 main: "#B3261E",
106 contrastText: "#FFFFFF",
107 },
108 }),
109 success: palette.augmentColor({
110 color: {
111 main: "#006e10",
112 contrastText: "#ffffff",
113 },
114 }),
115 info: palette.augmentColor({
116 color: {
117 main: "#0062a2",
118 contrastText: "#ffffff",
119 },
120 }),
121 warning: palette.augmentColor({
122 color: {
123 main: "#606200",
124 contrastText: "#313300",
125 },
126 }),
127 divider: "#79747E",
128 upvote: palette.augmentColor({
129 color: {
130 main: "#bd0b25",
131 contrastText: "#ffffff",
132 },
133 }),
134 downvote: palette.augmentColor({
135 color: {
136 main: "#006874",
137 contrastText: "#ffffff",
138 },
139 }),
140 containerPrimary: palette.augmentColor({
141 color: {
142 main: "#ffdad6",
143 contrastText: "#410005",
144 },
145 }),
146 containerSecondary: palette.augmentColor({
147 color: {
148 main: "#ffdad8",
149 contrastText: "#2d1514",
150 },
151 }),
152 },
153 },
154};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5export const theme = {
6 dark: {
7 palette: {
8 mode: "dark",
9 primary: palette.augmentColor({
10 color: {
11 main: "#acd452",
12 contrastText: "#253600",
13 },
14 }),
15 secondary: palette.augmentColor({
16 color: {
17 main: "#c2caaa",
18 contrastText: "#2c331c",
19 },
20 }),
21 text: {
22 primary: "#e4e2db",
23 secondary: "#e4e2db",
24 },
25 background: {
26 default: "#1b1c17",
27 paper: "#1b1c17",
28 },
29 error: palette.augmentColor({
30 color: {
31 main: "#ffb4a9",
32 contrastText: "#680003",
33 },
34 }),
35 success: palette.augmentColor({
36 color: {
37 main: "#79dd72",
38 contrastText: "#003a03",
39 },
40 }),
41 info: palette.augmentColor({
42 color: {
43 main: "#0062a2",
44 contrastText: "#ffffff",
45 },
46 }),
47 warning: palette.augmentColor({
48 color: {
49 main: "#606200",
50 contrastText: "#ffffff",
51 },
52 }),
53 divider: "#909284",
54 upvote: palette.augmentColor({
55 color: {
56 main: "#acd452",
57 contrastText: "#253600",
58 },
59 }),
60 downvote: palette.augmentColor({
61 color: {
62 main: "#ffb4a9",
63 contrastText: "#680003",
64 },
65 }),
66 containerPrimary: palette.augmentColor({
67 color: {
68 main: "#374e00",
69 contrastText: "#c8f16c",
70 },
71 }),
72 containerSecondary: palette.augmentColor({
73 color: {
74 main: "#3d4a36",
75 contrastText: "#d8e8cb",
76 },
77 }),
78 },
79 },
80 light: {
81 palette: {
82 mode: "light",
83 primary: palette.augmentColor({
84 color: {
85 main: "#4a6800",
86 contrastText: "#ffffff",
87 },
88 }),
89 secondary: palette.augmentColor({
90 color: {
91 main: "#5a6147",
92 contrastText: "#ffffff",
93 },
94 }),
95 text: {
96 primary: "#1b1c17",
97 secondary: "#1b1c17",
98 },
99 background: {
100 default: "#fefdf4",
101 paper: "#fefcf4",
102 },
103 error: palette.augmentColor({
104 color: {
105 main: "#ba1b1b",
106 contrastText: "#ffffff",
107 },
108 }),
109 success: palette.augmentColor({
110 color: {
111 main: "#006e10",
112 contrastText: "#ffffff",
113 },
114 }),
115 info: palette.augmentColor({
116 color: {
117 main: "#0062a2",
118 contrastText: "#ffffff",
119 },
120 }),
121 warning: palette.augmentColor({
122 color: {
123 main: "#606200",
124 contrastText: "#ffffff",
125 },
126 }),
127 divider: "#75786a",
128 upvote: palette.augmentColor({
129 color: {
130 main: "#4a6800",
131 contrastText: "#ffffff",
132 },
133 }),
134 downvote: palette.augmentColor({
135 color: {
136 main: "#ba1b1b",
137 contrastText: "#ffffff",
138 },
139 }),
140 containerPrimary: palette.augmentColor({
141 color: {
142 main: "#c8f16c",
143 contrastText: "#131f00",
144 },
145 }),
146 containerSecondary: palette.augmentColor({
147 color: {
148 main: "#d8e8cb",
149 contrastText: "#131f0e",
150 },
151 }),
152 },
153 },
154};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5export const theme = {
6 dark: {
7 palette: {
8 mode: "dark",
9 primary: palette.augmentColor({
10 color: {
11 main: "#acd452",
12 contrastText: "#253600",
13 },
14 }),
15 secondary: palette.augmentColor({
16 color: {
17 main: "#c2caaa",
18 contrastText: "#2c331c",
19 },
20 }),
21 text: {
22 primary: "#e4e2db",
23 secondary: "#e4e2db",
24 },
25 background: {
26 default: "#1b1c17",
27 paper: "#1b1c17",
28 },
29 error: palette.augmentColor({
30 color: {
31 main: "#ffb4a9",
32 contrastText: "#680003",
33 },
34 }),
35 success: palette.augmentColor({
36 color: {
37 main: "#79dd72",
38 contrastText: "#003a03",
39 },
40 }),
41 info: palette.augmentColor({
42 color: {
43 main: "#0062a2",
44 contrastText: "#ffffff",
45 },
46 }),
47 warning: palette.augmentColor({
48 color: {
49 main: "#606200",
50 contrastText: "#ffffff",
51 },
52 }),
53 divider: "#909284",
54 upvote: palette.augmentColor({
55 color: {
56 main: "#acd452",
57 contrastText: "#253600",
58 },
59 }),
60 downvote: palette.augmentColor({
61 color: {
62 main: "#ffb4a9",
63 contrastText: "#680003",
64 },
65 }),
66 containerPrimary: palette.augmentColor({
67 color: {
68 main: "#374e00",
69 contrastText: "#c8f16c",
70 },
71 }),
72 containerSecondary: palette.augmentColor({
73 color: {
74 main: "#3d4a36",
75 contrastText: "#d8e8cb",
76 },
77 }),
78 },
79 },
80 light: {
81 palette: {
82 mode: "light",
83 primary: palette.augmentColor({
84 color: {
85 main: "#4a6800",
86 contrastText: "#ffffff",
87 },
88 }),
89 secondary: palette.augmentColor({
90 color: {
91 main: "#5a6147",
92 contrastText: "#ffffff",
93 },
94 }),
95 text: {
96 primary: "#1b1c17",
97 secondary: "#1b1c17",
98 },
99 background: {
100 default: "#fefdf4",
101 paper: "#fefcf4",
102 },
103 error: palette.augmentColor({
104 color: {
105 main: "#ba1b1b",
106 contrastText: "#ffffff",
107 },
108 }),
109 success: palette.augmentColor({
110 color: {
111 main: "#006e10",
112 contrastText: "#ffffff",
113 },
114 }),
115 info: palette.augmentColor({
116 color: {
117 main: "#0062a2",
118 contrastText: "#ffffff",
119 },
120 }),
121 warning: palette.augmentColor({
122 color: {
123 main: "#606200",
124 contrastText: "#ffffff",
125 },
126 }),
127 divider: "#75786a",
128 upvote: palette.augmentColor({
129 color: {
130 main: "#4a6800",
131 contrastText: "#ffffff",
132 },
133 }),
134 downvote: palette.augmentColor({
135 color: {
136 main: "#ba1b1b",
137 contrastText: "#ffffff",
138 },
139 }),
140 containerPrimary: palette.augmentColor({
141 color: {
142 main: "#c8f16c",
143 contrastText: "#131f00",
144 },
145 }),
146 containerSecondary: palette.augmentColor({
147 color: {
148 main: "#d8e8cb",
149 contrastText: "#131f0e",
150 },
151 }),
152 },
153 },
154};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5const defaultLight = createTheme({
6 palette: {
7 mode: "light",
8 },
9});
10
11const defaultDark = createTheme({
12 palette: {
13 mode: "dark",
14 },
15});
16
17export const theme = {
18 dark: {
19 palette: {
20 ...defaultDark.palette,
21 upvote: palette.augmentColor({
22 color: {
23 main: "#66bb6a",
24 contrastText: "rgba(0,0,0,0.87)",
25 },
26 }),
27 downvote: palette.augmentColor({
28 color: {
29 main: "#f44336",
30 contrastText: "#fff",
31 },
32 }),
33 containerPrimary: palette.augmentColor({
34 color: {
35 main: "#121212",
36 contrastText: "white",
37 },
38 }),
39 containerSecondary: palette.augmentColor({
40 color: {
41 main: "#121212",
42 contrastText: "white",
43 },
44 }),
45 },
46 },
47 light: {
48 palette: {
49 ...defaultLight.palette,
50 upvote: palette.augmentColor({
51 color: {
52 main: "#2e7d32",
53 contrastText: "#32009a",
54 },
55 }),
56 downvote: palette.augmentColor({
57 color: {
58 main: "#d32f2f",
59 contrastText: "#fff",
60 },
61 }),
62 containerPrimary: palette.augmentColor({
63 color: {
64 main: "#fff",
65 contrastText: "#black",
66 },
67 }),
68 containerSecondary: palette.augmentColor({
69 color: {
70 main: "#fff",
71 contrastText: "#black",
72 },
73 }),
74 },
75 },
76};
1import { createTheme } from "@mui/material/styles";
2
3const { palette } = createTheme();
4
5const defaultLight = createTheme({
6 palette: {
7 mode: "light",
8 },
9});
10
11const defaultDark = createTheme({
12 palette: {
13 mode: "dark",
14 },
15});
16
17export const theme = {
18 dark: {
19 palette: {
20 ...defaultDark.palette,
21 upvote: palette.augmentColor({
22 color: {
23 main: "#66bb6a",
24 contrastText: "rgba(0,0,0,0.87)",
25 },
26 }),
27 downvote: palette.augmentColor({
28 color: {
29 main: "#f44336",
30 contrastText: "#fff",
31 },
32 }),
33 containerPrimary: palette.augmentColor({
34 color: {
35 main: "#121212",
36 contrastText: "white",
37 },
38 }),
39 containerSecondary: palette.augmentColor({
40 color: {
41 main: "#121212",
42 contrastText: "white",
43 },
44 }),
45 },
46 },
47 light: {
48 palette: {
49 ...defaultLight.palette,
50 upvote: palette.augmentColor({
51 color: {
52 main: "#2e7d32",
53 contrastText: "#32009a",
54 },
55 }),
56 downvote: palette.augmentColor({
57 color: {
58 main: "#d32f2f",
59 contrastText: "#fff",
60 },
61 }),
62 containerPrimary: palette.augmentColor({
63 color: {
64 main: "#fff",
65 contrastText: "#black",
66 },
67 }),
68 containerSecondary: palette.augmentColor({
69 color: {
70 main: "#fff",
71 contrastText: "#black",
72 },
73 }),
74 },
75 },
76};

Okay this is done, now how do we tell typescript that if I do <Button colour={’upvote’} /> then don’t throw an error? because I’ve supplied the values here in this object? also we haven’t added proper types to our current theme files. lets create some types.

create a file called index.ts at src/theme/index.ts

add the following code and read along:

1import { theme as green } from "./presets/green";
2import { theme as blue } from "./presets/blue";
3import { theme as _default } from "./presets/default";
4import { theme as red } from "./presets/red";
5import { Palette, PaletteColor } from "@mui/material/styles";
6
7// this is a typescript utility, if I say DeepPartial<Object> it means any key of that object, is not reauired.
8// this works even when we have nested objects and we want all the keys to be optional. why is this being used?
9// I'd recommend you try to omit this at the end of the tutorial to findout the errors you get to understand it's importance
10type DeepPartial<T> = {
11 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
12};
13
14declare module "@mui/material/styles" {
15 // these are the extra keys we added to our theme palette if you recall
16 // we are telling TS to chill out incase it encounters these keys
17 interface Palette {
18 upvote?: PaletteColor;
19 downvote?: PaletteColor;
20 containerPrimary?: PaletteColor;
21 containerSecondary?: PaletteColor;
22 }
23 // we need to supply it here too, PaletteOptions are used while supplying theme to the context
24 interface PaletteOptions {
25 upvote?: PaletteColor;
26 downvote?: PaletteColor;
27 containerPrimary?: PaletteColor;
28 containerSecondary?: PaletteColor;
29 }
30}
31
32// [IMP] in order to use colour={'upvote'} you need to tell ts this like so:
33declare module "@mui/material/Button" {
34 interface ButtonPropsColorOverrides {
35 upvote: true;
36 downvote: true;
37 }
38}
39
40// similarly i want to use upvote as a colour in circular progress component as well
41declare module "@mui/material/CircularProgress" {
42 interface CircularProgressPropsColorOverrides {
43 upvote: true;
44 downvote: true;
45 }
46}
47
48// this will be our Theme Type. remember how we created themes earlier? those objects
49// are of type AppTheme, we will add this type to those files
50export interface AppTheme {
51 dark: {
52 palette: DeepPartial<Palette>;
53 };
54 light: {
55 palette: DeepPartial<Palette>;
56 };
57}
58
59// finally we export a final object that contains all our themes which we can
60// use to pick our desired palette.
61export const color = {
62 0: _default,
63 1: green,
64 2: blue,
65 3: red,
66};
1import { theme as green } from "./presets/green";
2import { theme as blue } from "./presets/blue";
3import { theme as _default } from "./presets/default";
4import { theme as red } from "./presets/red";
5import { Palette, PaletteColor } from "@mui/material/styles";
6
7// this is a typescript utility, if I say DeepPartial<Object> it means any key of that object, is not reauired.
8// this works even when we have nested objects and we want all the keys to be optional. why is this being used?
9// I'd recommend you try to omit this at the end of the tutorial to findout the errors you get to understand it's importance
10type DeepPartial<T> = {
11 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
12};
13
14declare module "@mui/material/styles" {
15 // these are the extra keys we added to our theme palette if you recall
16 // we are telling TS to chill out incase it encounters these keys
17 interface Palette {
18 upvote?: PaletteColor;
19 downvote?: PaletteColor;
20 containerPrimary?: PaletteColor;
21 containerSecondary?: PaletteColor;
22 }
23 // we need to supply it here too, PaletteOptions are used while supplying theme to the context
24 interface PaletteOptions {
25 upvote?: PaletteColor;
26 downvote?: PaletteColor;
27 containerPrimary?: PaletteColor;
28 containerSecondary?: PaletteColor;
29 }
30}
31
32// [IMP] in order to use colour={'upvote'} you need to tell ts this like so:
33declare module "@mui/material/Button" {
34 interface ButtonPropsColorOverrides {
35 upvote: true;
36 downvote: true;
37 }
38}
39
40// similarly i want to use upvote as a colour in circular progress component as well
41declare module "@mui/material/CircularProgress" {
42 interface CircularProgressPropsColorOverrides {
43 upvote: true;
44 downvote: true;
45 }
46}
47
48// this will be our Theme Type. remember how we created themes earlier? those objects
49// are of type AppTheme, we will add this type to those files
50export interface AppTheme {
51 dark: {
52 palette: DeepPartial<Palette>;
53 };
54 light: {
55 palette: DeepPartial<Palette>;
56 };
57}
58
59// finally we export a final object that contains all our themes which we can
60// use to pick our desired palette.
61export const color = {
62 0: _default,
63 1: green,
64 2: blue,
65 3: red,
66};

small change before we move forward:

in ALL the four files blue.ts red.ts green.ts default.ts make the following change:

\import this newly created AppTheme type (add this entire line at the top of the file):

import { AppTheme } from "..";

use it to add explicit typing to our theme object (add ‘: AppTheme’ in front of ‘theme’):

export const theme: AppTheme = {

That’s it!

if you feel confused about anything, checkout blue.ts source here: https://github.com/facinick/MuiTheming/blob/master/src/theme/presets/blue.ts

if your code looks the same, move ahead.

Let’s go back to our Theme provider and get them themes!

Following is the file src/theme/ThemeProvider.tsx after new changes, follow the comments in the code below:

1import CssBaseline from "@mui/material/CssBaseline";
2import GlobalStyles from "@mui/material/GlobalStyles";
3import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
4import useMediaQuery from "@mui/material/useMediaQuery";
5import React from "react";
6import { useEffect } from "react";
7// 1. import our newly fresh yum exported theme
8import { color as ThemeColors } from "./index";
9
10export const ThemeContext = React.createContext({
11 toggleColorMode: () => {},
12 shuffleColorTheme: () => {},
13});
14
15type MyThemeProviderProps = {
16 children?: React.ReactNode;
17};
18
19export default function MyThemeProvider(props: MyThemeProviderProps) {
20 const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
21
22 const [mode, setMode] = React.useState<"light" | "dark">(
23 prefersDarkMode ? "dark" : "light"
24 );
25 const [theme, setTheme] = React.useState<0 | 1 | 2 | 3>(0);
26
27 useEffect(() => {
28 setMode(prefersDarkMode ? "dark" : "light");
29 }, [prefersDarkMode]);
30
31 const colorMode = React.useMemo(
32 () => ({
33 toggleColorMode: () => {
34 setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
35 },
36 shuffleColorTheme: () => {
37 setTheme((prevTheme) => ((prevTheme + 1) % 4) as 0 | 1 | 2 | 3);
38 },
39 }),
40 []
41 );
42
43// 2. create theme object, pick theme from the mega theme object we exported earlier
44// based on our theme and mode values
45 const _theme = React.useMemo(
46 () => createTheme(ThemeColors[theme][mode] as ThemeOptions),
47 [mode, theme]
48 );
49
50 return (
51 <ThemeContext.Provider value={colorMode}>
52 {/* 3. supply it to MUI ThemeProvider */}
53 <ThemeProvider theme={_theme}>
54 <GlobalStyles styles={{}} />
55 <CssBaseline enableColorScheme />
56 {props.children}
57 </ThemeProvider>
58 </ThemeContext.Provider>
59 );
60}
1import CssBaseline from "@mui/material/CssBaseline";
2import GlobalStyles from "@mui/material/GlobalStyles";
3import { ThemeProvider, createTheme, ThemeOptions } from "@mui/material/styles";
4import useMediaQuery from "@mui/material/useMediaQuery";
5import React from "react";
6import { useEffect } from "react";
7// 1. import our newly fresh yum exported theme
8import { color as ThemeColors } from "./index";
9
10export const ThemeContext = React.createContext({
11 toggleColorMode: () => {},
12 shuffleColorTheme: () => {},
13});
14
15type MyThemeProviderProps = {
16 children?: React.ReactNode;
17};
18
19export default function MyThemeProvider(props: MyThemeProviderProps) {
20 const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
21
22 const [mode, setMode] = React.useState<"light" | "dark">(
23 prefersDarkMode ? "dark" : "light"
24 );
25 const [theme, setTheme] = React.useState<0 | 1 | 2 | 3>(0);
26
27 useEffect(() => {
28 setMode(prefersDarkMode ? "dark" : "light");
29 }, [prefersDarkMode]);
30
31 const colorMode = React.useMemo(
32 () => ({
33 toggleColorMode: () => {
34 setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
35 },
36 shuffleColorTheme: () => {
37 setTheme((prevTheme) => ((prevTheme + 1) % 4) as 0 | 1 | 2 | 3);
38 },
39 }),
40 []
41 );
42
43// 2. create theme object, pick theme from the mega theme object we exported earlier
44// based on our theme and mode values
45 const _theme = React.useMemo(
46 () => createTheme(ThemeColors[theme][mode] as ThemeOptions),
47 [mode, theme]
48 );
49
50 return (
51 <ThemeContext.Provider value={colorMode}>
52 {/* 3. supply it to MUI ThemeProvider */}
53 <ThemeProvider theme={_theme}>
54 <GlobalStyles styles={{}} />
55 <CssBaseline enableColorScheme />
56 {props.children}
57 </ThemeProvider>
58 </ThemeContext.Provider>
59 );
60}

We are 99% done, let’s move to the final part of the tutorial:

What’s left?

we have created bunch of themes.

we have a context that provides two functions to change theme and modes.

we are picking out the correct theme and supplying it to MUI provider.

we still need to pick the theme and apply it to our components!

Open src/App.tsx and add the following code

1import React from "react";
2import Container from "@mui/material/Container";
3import {
4 Box,
5 Card,
6 CardContent,
7 Checkbox,
8 CircularProgress,
9 Slider,
10 Switch,
11 TextField,
12 TextFieldProps,
13 Typography,
14 useTheme,
15} from "@mui/material";
16import Button from "@mui/material/Button";
17import Stack from "@mui/material/Stack";
18import { styled } from "@mui/material/styles";
19
20// how to change theme and modes? check below:
21
22// this is a Component that'll on button click
23// read the colorMode from context (which had our two functions to change theme and mode)
24// use colorMode to call the toggleColorMode function
25const ThemeModeSwitch = () => {
26 const theme = useTheme();
27 const colorMode = React.useContext(ThemeContext);
28 return (
29 <ToggleButton
30 style={{ borderRadius: "50px", border: "none" }}
31 value="check"
32 onChange={colorMode.toggleColorMode}
33 >
34 change mode
35 {theme.palette.mode === "dark" && <LightModeIcon color={"secondary"} />}
36 {theme.palette.mode === "light" && <DarkModeIcon color={"secondary"} />}
37 </ToggleButton>
38 );
39};
40
41// this is a Component that'll on button click
42// read the colorMode from context (which had our two functions to change theme and mode)
43// use colorMode to call the shuffleColorTheme function
44const ThemeSwitch = () => {
45 const colorMode = React.useContext(ThemeContext);
46 return (
47 <ToggleButton
48 value={"check"}
49 style={{ borderRadius: "50px", border: "none" }}
50 onChange={colorMode.shuffleColorTheme}
51 >
52 change theme
53 <ColorLensIcon color={"secondary"} />
54 </ToggleButton>
55 );
56};
57// see? because of context api, we didn't have to pass those functions all the way down till here. We
58// can just React.useContext(Context);
59
60// this is a way to create styled MUI components with our theme values
61// here I want the input field to have font color I've chosen for containerSecondary
62// and background color of containerSecondary
63// I could pick current themes colors like theme.palette.primary.main
64// or theme.palette.upvote.main etc
65
66const StyledTextField = styled(TextField)<TextFieldProps>(({ theme }) => ({
67 color: theme?.palette?.containerSecondary?.contrastText,
68 backgroundColor: theme?.palette?.containerSecondary?.main,
69 "& .MuiInputBase-root": {
70 color: theme?.palette?.containerSecondary?.contrastText,
71 },
72 "& .MuiInputLabel-root": {
73 color: theme?.palette?.containerSecondary?.contrastText,
74 },
75}));
76
77export const Main = () => {
78 // we can also get theme this way
79 const theme = useTheme();
80
81 return (
82 <Card>
83 <CardContent>
84 <Stack direction="column" spacing={5}>
85 <Stack direction="row" spacing={5}>
86 {/* Our buttons to toggle theme and modes */}
87 <ThemeModeSwitch />
88 <ThemeSwitch />
89 </Stack>
90 <Stack direction="row" spacing={5}>
91 <Button variant={"contained"} color={"primary"}>
92 Primary Button
93 </Button>
94 <Button variant={"contained"} color={"secondary"}>
95 Secondary Button
96 </Button>
97 <Button variant={"contained"} color={"upvote"}>
98 Custom Button
99 </Button>
100 </Stack>
101 <Stack direction="row" spacing={5}>
102 <TextField value={"Un styled text field"} color={"primary"} />
103 <StyledTextField
104 value={"styled text field"}
105 multiline
106 size="small"
107 rows={4}
108 />
109 </Stack>
110 <Stack alignItems={"center"} direction="row" spacing={5}>
111 <Switch defaultChecked />
112 <Checkbox color={"primary"} defaultChecked />
113 <Checkbox color={"error"} defaultChecked />
114 <Checkbox color={"secondary"} defaultChecked />
115 <Slider />
116 </Stack>
117 <Stack alignItems={"center"} direction="row" spacing={5}>
118 <Box
119 {/* Here I am using the theme variables to style my box element to container secondary */}
120 sx={{
121 color: theme?.palette?.containerSecondary?.contrastText,
122 backgroundColor: theme?.palette?.containerSecondary?.main,
123 padding: 1,
124 }}
125 >
126 <Typography>Use Theme</Typography>
127 </Box>
128 <CircularProgress color={"downvote"} />
129 </Stack>
130 </Stack>
131 </CardContent>
132 </Card>
133 );
134};
135
136function App() {
137 return (
138 <div className="app">
139 <Container maxWidth="md">
140 <Main />
141 </Container>
142 </div>
143 );
144}
145
146export default App;
1import React from "react";
2import Container from "@mui/material/Container";
3import {
4 Box,
5 Card,
6 CardContent,
7 Checkbox,
8 CircularProgress,
9 Slider,
10 Switch,
11 TextField,
12 TextFieldProps,
13 Typography,
14 useTheme,
15} from "@mui/material";
16import Button from "@mui/material/Button";
17import Stack from "@mui/material/Stack";
18import { styled } from "@mui/material/styles";
19
20// how to change theme and modes? check below:
21
22// this is a Component that'll on button click
23// read the colorMode from context (which had our two functions to change theme and mode)
24// use colorMode to call the toggleColorMode function
25const ThemeModeSwitch = () => {
26 const theme = useTheme();
27 const colorMode = React.useContext(ThemeContext);
28 return (
29 <ToggleButton
30 style={{ borderRadius: "50px", border: "none" }}
31 value="check"
32 onChange={colorMode.toggleColorMode}
33 >
34 change mode
35 {theme.palette.mode === "dark" && <LightModeIcon color={"secondary"} />}
36 {theme.palette.mode === "light" && <DarkModeIcon color={"secondary"} />}
37 </ToggleButton>
38 );
39};
40
41// this is a Component that'll on button click
42// read the colorMode from context (which had our two functions to change theme and mode)
43// use colorMode to call the shuffleColorTheme function
44const ThemeSwitch = () => {
45 const colorMode = React.useContext(ThemeContext);
46 return (
47 <ToggleButton
48 value={"check"}
49 style={{ borderRadius: "50px", border: "none" }}
50 onChange={colorMode.shuffleColorTheme}
51 >
52 change theme
53 <ColorLensIcon color={"secondary"} />
54 </ToggleButton>
55 );
56};
57// see? because of context api, we didn't have to pass those functions all the way down till here. We
58// can just React.useContext(Context);
59
60// this is a way to create styled MUI components with our theme values
61// here I want the input field to have font color I've chosen for containerSecondary
62// and background color of containerSecondary
63// I could pick current themes colors like theme.palette.primary.main
64// or theme.palette.upvote.main etc
65
66const StyledTextField = styled(TextField)<TextFieldProps>(({ theme }) => ({
67 color: theme?.palette?.containerSecondary?.contrastText,
68 backgroundColor: theme?.palette?.containerSecondary?.main,
69 "& .MuiInputBase-root": {
70 color: theme?.palette?.containerSecondary?.contrastText,
71 },
72 "& .MuiInputLabel-root": {
73 color: theme?.palette?.containerSecondary?.contrastText,
74 },
75}));
76
77export const Main = () => {
78 // we can also get theme this way
79 const theme = useTheme();
80
81 return (
82 <Card>
83 <CardContent>
84 <Stack direction="column" spacing={5}>
85 <Stack direction="row" spacing={5}>
86 {/* Our buttons to toggle theme and modes */}
87 <ThemeModeSwitch />
88 <ThemeSwitch />
89 </Stack>
90 <Stack direction="row" spacing={5}>
91 <Button variant={"contained"} color={"primary"}>
92 Primary Button
93 </Button>
94 <Button variant={"contained"} color={"secondary"}>
95 Secondary Button
96 </Button>
97 <Button variant={"contained"} color={"upvote"}>
98 Custom Button
99 </Button>
100 </Stack>
101 <Stack direction="row" spacing={5}>
102 <TextField value={"Un styled text field"} color={"primary"} />
103 <StyledTextField
104 value={"styled text field"}
105 multiline
106 size="small"
107 rows={4}
108 />
109 </Stack>
110 <Stack alignItems={"center"} direction="row" spacing={5}>
111 <Switch defaultChecked />
112 <Checkbox color={"primary"} defaultChecked />
113 <Checkbox color={"error"} defaultChecked />
114 <Checkbox color={"secondary"} defaultChecked />
115 <Slider />
116 </Stack>
117 <Stack alignItems={"center"} direction="row" spacing={5}>
118 <Box
119 {/* Here I am using the theme variables to style my box element to container secondary */}
120 sx={{
121 color: theme?.palette?.containerSecondary?.contrastText,
122 backgroundColor: theme?.palette?.containerSecondary?.main,
123 padding: 1,
124 }}
125 >
126 <Typography>Use Theme</Typography>
127 </Box>
128 <CircularProgress color={"downvote"} />
129 </Stack>
130 </Stack>
131 </CardContent>
132 </Card>
133 );
134};
135
136function App() {
137 return (
138 <div className="app">
139 <Container maxWidth="md">
140 <Main />
141 </Container>
142 </div>
143 );
144}
145
146export default App;

when you supply colour={’primary’} to a button, it’ll automatically pick background and text colour from theme

but in case of let’s say Box, you’ll have to specify background and font colour as we are doing above.

generally, like the name suggests,

use primary colours for important UI elements that will draw user attention

use secondary colours for not so important ui elements

use container colours for well container elements like cards, boxes, paper etc.

again, primary containers are important UI containers and primary container contrastText is the text to be shown on it


Resources

  1. MUI Default theme: https://mui.com/material-ui/customization/default-theme/
  2. Material You Theme Builder: https://material-foundation.github.io/material-theme-builder/#/custom
  3. Demo: https://mui-theming.vercel.app/
  4. MUI Components: https://mui.com/components/
  5. useMemo hook: https://dmitripavlutin.com/react-usememo-hook/

Tips

  1. Make sure you \import methods, classes, variables etc from correct package.
  2. Make sure you extend typescript definition of MUI theme to incorporate your custom variables
Published By