← Back to DevBytes

Material UI TypeScript: Strongly Typed Applications

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:

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

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles