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.
Thank you so much for writing this! I was not sure where to start