How to properly extend Material-UI theme in TypeScript

Toru Eguchi
Sep 21, 2019 · 6 min read

Material-UI is one of the most popular React component library in the world because it’s easy to use and fast. The best thing of all is looking good ! On the other hand, a lot of React users use TypeScript with React these days because it helps many potential run time errors especially for large applications. Combination of React and TypeScript is super powerful and beautiful. So it was meant to be that two of them met.

As you use, you could know integrating TypeScript to Material-UI is not difficult. You can find good docs in Material-UI website. But one difficult thing you encounter is about theme, especially palette. Unfortunately, default Material-UI theme is bit limited. So this article explains how you can extend Material-UI theme in TypeScript. Actually, if you master this article, you could extend other types !

The problem of Material-UI theme

As I explained above, default Material-UI theme is bit limited like below.

theme.tstheme.palette
├──── primary
│ ├──── main: string;
│ ├──── dark: string;
│ ├──── light: string;
│ ├──── contrastText: string;
│ ├──── 50: string;
│ ├──── 100: string;
│ ├──── 200: string;
│ ├──── 300: string;
│ ├──── 400: string;
│ ├──── 600: string;
│ ├──── 700: string;
│ ├──── 800: string;
│ ├──── 900: string;
│ ├──── A100: string;
│ ├──── A200: string;
│ ├──── A400: string;
│ └──── A700: string;

├──── secondary
│ ...same as primary

├──── error
│ ...same as primary

|____ text
│ ├──── primary: string;
│ ├──── secondary: string;
│ ├──── disabled: string;
│ └──── hint: string;

|____ divider: string;

|____ action
│ ├──── active: string;
│ ├──── hover: string;
│ ├──── hoverOpacity: string;
│ ├──── selected: string;
│ ├──── disabled: string;
│ └──── disabledBackground: string;
|
|____ background
├──── default: string;
└──── card: string;

You normally assign one color to one palette option. For example, primary color is red, secondary is blue. But when you create a theme, you might want more palette options like below.

theme.palette
├──── primary
├──── secondary
├──── error
├──── grey
|____ text
|____ divider
|____ action
|____ background
|____ success
green here??
|____ failure
red more??
|____ warning
yellow here??
|____ info
gray here??

But when you try it, you would get the error like below.

The key message is the last one. Object literal may only specify known properties, and ‘success' does not exist in type ‘PaletteOptions’. It means that because Material-UI package already provide the type declaration of palette options, you can’t add extra keys to it.

So how you can use extra keys? The answer is extending Material UI theme type by yourself.

Extend Material-UI theme in TypeScript

As you checked above, the target type you should extend is PaletteOptions. So first of all, let’s check the default declarations. You can find them in node_modules/@material-ui/core/styles/createPalette.d.ts or the original GitHub page.

node_modules/@material-ui/core/styles/createPalette.d.ts...
import { Color, PaletteType } from '..';
...
export interface PaletteOptions {
primary?: PaletteColorOptions;
secondary?: PaletteColorOptions;
error?: PaletteColorOptions;
type?: PaletteType;
tonalOffset?: number;
contrastThreshold?: number;
common?: Partial<CommonColors>;
grey?: ColorPartial;
text?: Partial<TypeText>;
divider?: string;
action?: Partial<TypeAction>;
background?: Partial<TypeBackground>;
getContrastText?: (background: string) => string;
}

Of course, there is no “success” in PaletteOptions keys. But now, you might guess that if you add keys to PaletteOptions, you can extend theme !

Please add createPalette.d.ts anywhere you like. Normally, you create types directory to your src directory and place type files to it.

And in createPalette.d.ts , you can write like below.

src/types/createPalette.d.tsimport * as createPalette from '@material-ui/core/styles/createPalette';declare module '@material-ui/core/styles/createPalette' {
interface PaletteOptions {
success?: PaletteColorOptions;
warning?: PaletteColorOptions;
}
}

When you want the same type of primary , you can just use PaletteColorOptions which is used for primary . So let’s add “success” key in theme.ts !

You don’t get an error ! On the contrary, you can get a recommendation !

For advanced users, you might need custom palette options. For example, if you use file icons for each extensions like pdf, csv, you need specific colors to each extensions. In such situation, you can create a custom type in createPalette.d.ts .

import * as createPalette from '@material-ui/core/styles/createPalette';declare module '@material-ui/core/styles/createPalette' {
interface IconPaletteColorOptions {
pdf?: string;
csv?: string;
}
interface PaletteOptions {
success?: PaletteColorOptions;
warning?: PaletteColorOptions;
icon?: IconPaletteColorOptions;
}
}

As you saw, you can create a new type and assign it to PaletteOptions.

Unfortunately, type extension is still not enough. When you try to use extended theme in each component, you would get an error.

Because theme is typed with Theme , the cause of this error might around Theme and Palette. Go to createPalette.d.ts again.

node_modules/@material-ui/core/styles/createPalette.d.ts...
export interface Palette {
common: CommonColors;
type: PaletteType;
contrastThreshold: number;
tonalOffset: number;
primary: PaletteColor;
secondary: PaletteColor;
error: PaletteColor;
grey: Color;
text: TypeText;
divider: TypeDivider;
action: TypeAction;
background: TypeBackground;
getContrastText: (background: string) => string;
augmentColor: {
(
color: ColorPartial,
mainShade?: number | string,
lightShade?: number | string,
darkShade?: number | string,
): PaletteColor;
(color: PaletteColorOptions): PaletteColor;
};
}
...

Palette is very similar to PaletteOptions . But both of Palette and PaletteOptions are exported. And they are used in createMuiTheme.d.ts . And you can find Theme type like below.

node_modules/@material-ui/core/styles/createMuiTheme.d.ts
...
export interface Theme {
shape: Shape;
breakpoints: Breakpoints;
direction: Direction;
mixins: Mixins;
overrides?: Overrides;
palette: Palette;
props?: ComponentsProps;
shadows: Shadows;
spacing: Spacing;
transitions: Transitions;
typography: Typography;
zIndex: ZIndex;
}

Yes ! You found it ! In Theme type declaration, Palette is used in palette key. So you should extend not only PaletteOptions but also Palette .

import * as createPalette from '@material-ui/core/styles/createPalette';declare module '@material-ui/core/styles/createPalette' {
interface IconPaletteColorOptions {
pdf?: string;
csv?: string;
}
interface IconPaletteColor {
pdf: string;
csv: string;
}
interface PaletteOptions {
success?: PaletteColorOptions;
warning?: PaletteColorOptions;
icon?: IconPaletteColorOptions;
}
interface Palette {
success: PaletteColor;
warning: PaletteColor;
icon: IconPaletteColor;
}
}

That’s it. Now you can perfectly use custom theme ! You don’t get any error !

In Material UI 4.8.1 release , warning, success and info were added ! So you don’t have to add them.

But unfortunately a real product is not simple as Material UI theme. I already added “failure” and “processing” to one of my company service. So it’s good to know how to edit it !


This article explains how you can extend Material-UI theme in TypeScript. But as you read through this article, you also understand how to extend type declaration in packages. If you understand how to extend type, your TypeScript skill is going to next level !

JavaScript in Plain English

Learn the web's most important programming language.

Toru Eguchi

Written by

I am a full-stack engineer. I have strength on React, Vue, Express, Rails (JS, TS, Ruby). Before that, I was an infrastructure engineer of AWS and GCP.

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade