• Please complete the contact form below with details about your inquiry and I'll get back to you as soon as possible.

  • This field is for validation purposes and should be left unchanged.

Material UI custom theme and TypeScript

If you’re creating a custom theme object and provide it to the ThemeProvider, you’ll notice that TypeScript complains when you’re reading a value from a custom property on the type Theme (ie in a makeStyles function). For example, if your custom theme object looks something like:

import { createTheme } from '@material-ui/core/styles';

// A custom theme for this app
const theme = {
    palette: {
        primary: {
            main: '#556cd6',
        },
        secondary: {
            main: '#19857b',
        },
        error: {
            main: red.A400,
        },
        background: {
            default: '#fff',
        },
    },
    sidebarWidth: 240
};

export default createTheme(theme);

, then when you want to use a value from the Theme, TypeScript will complain that this property is not defined on the type Theme:

import {makeStyles, createStyles} from '@material-ui/core'

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        sidebar: {
            // Property 'sidebarWidth' does not exist on type Theme
            width: theme.sidebarWidth
        }
    })
)

Material UI docs suggest to exploit TypeScript’s module augmentation feature to extend the Theme type with your custom properties. However, you still need to manually define those properties and their types. Wouldn’t it be nice for TypeScript to infer those properties and their values from our theme object? By using module augmentation, mapped types, and const assertions, you can achieve that like this:

import { createTheme } from '@material-ui/core/styles';

// A custom theme for this app
const theme = {
    palette: {
        primary: {
            main: '#556cd6',
        },
        secondary: {
            main: '#19857b',
        },
        error: {
            main: red.A400,
        },
        background: {
            default: '#fff',
        },
    },
    sidebarWidth: 240
} as const;

type CustomTheme = {
    [Key in keyof typeof theme]: typeof theme[Key]
}

declare module '@material-ui/core/styles/createTheme' {
    interface Theme extends CustomTheme { }
    interface ThemeOptions extends CustomTheme { }
}

export default createTheme(theme);

Here, module augmentation allows us to extend the Theme (this is used as return type wherever a value from the theme object is read) and ThemeOptions (this is used as an input type with functions like createTheme). We use a mapped type CustomTheme to take the possible properties and their value types from our actual theme object. And we use const assertions (used as as const above), because without it the return type would be the actual type (theme.sidebarWidth would show number, instead of 240) instead of the literal type, and when using a value from the theme object we’re most likely to be interested in the actual value instead of its type.

Leave a Reply

Your email address will not be published. Required fields are marked *