What Are Material UI and TypeScript?
Material UI (MUI) is a comprehensive React component library that implements Google’s Material Design principles. It provides a rich set of pre-built, customizable UI elements like buttons, dialogs, tables, and form controls. MUI v5 and later versions are built with first-class TypeScript support, offering precise type definitions straight out of the box.
TypeScript is a strongly typed superset of JavaScript that brings static type checking, interfaces, generics, and powerful tooling to your development workflow. When you combine Material UI with TypeScript, you gain a development experience where every component prop, theme variable, and style function is fully typed. This tutorial will walk you through creating a strongly typed MUI application, explaining why it matters, how to implement it step by step, and the best practices to follow.
Why Strong Typing Matters in MUI Applications
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Strong typing transforms your relationship with the component library from “hoping things work” to “knowing they work.” Here’s why it’s essential:
- Autocomplete & IntelliSense: Your IDE instantly suggests valid props, theme properties, and callback signatures as you type.
- Compile-time error detection: Misspelled props, wrong types for callbacks, or invalid theme accesses are caught before you even open a browser.
- Self-documenting code: Interfaces and types serve as living documentation, making large codebases easier to understand and refactor.
- Custom theme safety: When you extend the default theme with your own properties, TypeScript ensures every component sees those additions correctly, eliminating runtime surprises.
- Streamlined collaboration: Teams can enforce consistent API usage and reduce integration bugs across features.
Setting Up a TypeScript + MUI Project
The quickest way to start is with Create React App’s TypeScript template. This gives you a working React setup with TypeScript already configured.
Scaffolding with Create React App
npx create-react-app my-mui-app --template typescript
cd my-mui-app
Installing MUI Dependencies
MUI v5 relies on Emotion for its styling engine. Install the core package, icons, and the required Emotion libraries.
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
After installation, MUI’s type definitions are automatically available because
the package includes its own .d.ts files. No additional
@types/ packages are needed.
Verifying TypeScript Configuration
Ensure your tsconfig.json has "strict": true (or at
least the individual strict options you need). Strict mode enables the full
power of TypeScript and catches more potential issues.
Strongly Typing the MUI Theme
MUI’s theme object controls colors, typography, spacing, and much more. By
default, createTheme returns a fully typed theme. But real-world
applications often need custom properties—brand-specific colors, status
indicators, or layout tokens. TypeScript lets you add these safely.
Default Theme Types
Every built-in theme path is typed. For example,
theme.palette.primary.main is a string,
theme.spacing(2) returns a number, and
theme.breakpoints.up('md') returns a specific string. You never
have to guess.
Extending the Theme with Module Augmentation
To add custom properties, you declare a module augmentation that merges your
definitions into MUI’s Theme and ThemeOptions
interfaces. Create a .d.ts file (e.g.,
src/mui-theme.d.ts) and add your extensions.
// src/mui-theme.d.ts
import '@mui/material/styles';
declare module '@mui/material/styles' {
interface Theme {
brand: {
gradient: string;
shadow: string;
};
status: {
danger: string;
warning: string;
success: string;
};
}
// allow configuration using `createTheme`
interface ThemeOptions {
brand?: {
gradient?: string;
shadow?: string;
};
status?: {
danger?: string;
warning?: string;
success?: string;
};
}
}
This augmentation merges seamlessly with MUI’s internal types. Any component
accessing theme.brand.gradient will get full type support.
Creating a Custom Theme with createTheme
Now use createTheme to build a theme that includes your custom
values. TypeScript will enforce that you provide the correct shape.
// src/theme.ts
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
},
brand: {
gradient: 'linear-gradient(45deg, #556cd6, #19857b)',
shadow: '0 4px 20px rgba(0,0,0,0.1)',
},
status: {
danger: '#ff1744',
warning: '#ff9100',
success: '#00e676',
},
});
export default theme;
Consuming the Typed Theme in Components
Use useTheme with a generic type parameter for extra clarity, or
simply rely on inference. Both approaches give you autocompletion for custom
properties.
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
const StatusBanner = () => {
const theme = useTheme();
// TypeScript knows theme.status.danger is a string
return (
<Box
sx={{
backgroundColor: theme.status.warning,
background: theme.brand.gradient,
padding: 2,
borderRadius: 1,
}}
>
Custom themed banner
</Box>
);
};
Typing Component Props
MUI exports dedicated prop types for almost every component. Using them keeps your custom components consistent and type-safe.
Using MUI’s Built-in Prop Types
For instance, ButtonProps covers every prop the Button component
accepts. You can spread them directly or use them to type your own wrapper.
import { Button, ButtonProps } from '@mui/material';
const SubmitButton: React.FC<ButtonProps> = (props) => {
return (
<Button variant="contained" color="primary" {...props}>
Submit
</Button>
);
};
Extending Props with Custom Attributes
When you need extra properties, extend the base prop type using intersection
(&) or interface extension.
import { Button, ButtonProps } from '@mui/material';
interface IconButtonProps extends ButtonProps {
icon: React.ReactNode;
label: string;
}
const ActionButton = ({ icon, label, ...rest }: IconButtonProps) => {
return (
<Button startIcon={icon} aria-label={label} {...rest}>
{label}
</Button>
);
};
TypeScript will now demand icon and label whenever
you use ActionButton, while still accepting all standard Button
props.
Working with Polymorphic Components
Components like Box and Typography accept a
component prop that changes the rendered root element. Their types
are generic, and TypeScript infers the correct allowed props based on the
chosen element.
import { Box, Typography } from '@mui/material';
// Renders as <section> and accepts section attributes
const SectionBox = () => (
<Box component="section" sx={{ py: 4 }}>
<Typography variant="h2" component="h1">
Typed heading
</Typography>
</Box>
);
Typing the sx Prop and System Styling
The sx prop is MUI’s powerful styling shorthand. It accesses theme
values and applies responsive styles. TypeScript ensures you only reference
valid theme paths and use correct style values.
Using SxProps<Theme> for Reusable Styles
Define your styles as typed objects and reuse them across components. The
SxProps<Theme> type guarantees that every key aligns with
MUI’s system.
import { SxProps, Theme } from '@mui/material';
import Box from '@mui/material/Box';
const cardStyle: SxProps<Theme> = {
backgroundColor: 'primary.main',
color: 'primary.contrastText',
padding: 3,
borderRadius: 2,
'&:hover': {
backgroundColor: 'primary.dark',
boxShadow: 4,
},
};
const StyledCard = () => (
<Box sx={cardStyle}>
This card uses typed system styles
</Box>
);
Creating Typed Styled Components
The styled API from MUI automatically infers types from the
element or component you pass. It also provides the theme via a callback.
import { styled } from '@mui/material/styles';
const CustomHeader = styled('header')(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
padding: theme.spacing(2, 4),
display: 'flex',
alignItems: 'center',
...theme.typography.h6,
}));
// Usage: <CustomHeader>...</CustomHeader> – all standard HTML attributes allowed
Typing Complex Components: Data Grid and Forms
MUI X packages (like Data Grid) provide generic types that let you define columns and rows with exact type safety. Forms benefit from typed event handlers and validation schemas.
Data Grid with Generic Row Type
Define an interface for your data, then use GridColDef with the
generic parameter. The Data Grid will enforce that column field names match
keys of your row type.
import { DataGrid, GridColDef } from '@mui/x-data-grid';
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
const columns: GridColDef<User>[] = [
{ field: 'id', headerName: 'ID', width: 70 },
{ field: 'name', headerName: 'Name', width: 150 },
{ field: 'email', headerName: 'Email', width: 200 },
{ field: 'role', headerName: 'Role', width: 120 },
];
const rows: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
];
const UsersTable = () => (
<div style={{ height: 400, width: '100%' }}>
<DataGrid rows={rows} columns={columns} />
</div>
);
Form Fields with Typed Value Handlers
MUI’s TextField, Select, and Autocomplete
components accept generic type parameters for their value. This ensures
onChange handlers receive the correct type.
import { useState } from 'react';
import { TextField, MenuItem } from '@mui/material';
type Status = 'pending' | 'active' | 'completed';
const StatusSelect = () => {
const [status, setStatus] = useState<Status>('pending');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setStatus(event.target.value as Status);
};
return (
<TextField
select
label="Status"
value={status}
onChange={handleChange}
fullWidth
>
{(['pending', 'active', 'completed'] as Status[]).map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</TextField>
);
};
Best Practices for Strongly Typed MUI Applications
-
Enable strict TypeScript mode: Set
"strict": trueintsconfig.json. This enablesnoImplicitAny,strictNullChecks, and more, catching a vast array of bugs early. -
Always augment the theme for custom properties: Never cast
the theme with
as anyor rely on unchecked string indices. Use module augmentation exactly as shown so that every access is typed. -
Use MUI’s exported prop types: For every component you wrap
or extend, import its
XPropstype (e.g.,ButtonProps,TextFieldProps). This future-proofs your code against MUI API changes. -
Type your
sxobjects: When storing styles in variables, explicitly type them asSxProps<Theme>. This validates theme references and responsive breakpoints. -
Leverage generic components correctly: For
Select,Autocomplete, and Data Grid, always provide the generic type parameter. It eliminates manual type assertions and catches mismatches. -
Avoid
Partialoranyfor props: Define exact interfaces for custom components. If a prop is optional, mark it with?instead of relaxing the whole type. -
Keep your custom theme file separate: Maintain
theme.tsandmui-theme.d.tsin a dedicated directory. Import and useThemeProviderat the root so the entire app benefits from your typed theme. -
Use
React.ComponentPropsfor third-party MUI components: When you need to extract the props of a specific MUI component without importing its named type, you can useReact.ComponentProps<typeof Component>as an alternative.
Conclusion
Combining Material UI with TypeScript elevates your React development from
fragile to resilient. By treating every theme extension, component prop, and
style object as a typed entity, you build an application that is
self-documenting, easier to refactor, and remarkably resistant to runtime
errors. The techniques covered—module augmentation, generic prop types,
typed sx definitions, and best practices around strict
configuration—form a solid foundation for any modern MUI project. Embrace
these patterns, and your team will spend less time debugging and more time
delivering polished, high-quality user interfaces.