| name | material-ui |
| description | [Applies to: **/*] Provides definitive guidelines for building scalable, performant, and maintainable React applications using Material-UI (MUI), focusing on modern best practices. |
| source | cursor_mdc |
Material-UI (MUI) Best Practices
This guide outlines the definitive best practices for developing React applications with Material-UI (MUI). Adhere to these standards to ensure consistent, performant, and maintainable code across our projects.
1. Code Organization & Structure
Organize code by feature, not by type. Components should be small, focused, and self-contained.
1.1 Directory Structure
Adopt a feature-oriented layout.
src/
components/ # Reusable, generic UI components (e.g., Button, Modal)
Button/
Button.jsx
Button.styled.js # Styled components for Button
Button.test.jsx
index.js # Exports Button
features/ # Feature-specific components and logic (e.g., UserProfile, ProductList)
Auth/
LoginPage.jsx
AuthForm.jsx
AuthService.js
AuthContext.js
theme/ # Centralized MUI theme configuration
index.js
palette.js
typography.js
utils/ # General utility functions
api.js
helpers.js
App.jsx # Main application component
index.jsx # Entry point
1.2 Naming Conventions
- Components:
PascalCase (e.g., MyComponent.jsx).
- Styled Components:
PascalCase for the component, .styled.js suffix (e.g., MyComponent.styled.js).
- CSS Modules:
kebab-case or camelCase (e.g., my-component.module.css).
- Tests:
.test.jsx suffix (e.g., MyComponent.test.jsx).
- Indexes:
index.js should export the main entity of its parent folder.
2. Theming
Centralize all design tokens and theme customizations. Always use createTheme and ThemeProvider.
2.1 Centralized Theme Configuration
Define your theme in src/theme/index.js and wrap your application with ThemeProvider. This is where Material Design 3 tokens should be configured.
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#006B5F',
light: '#4DA89F',
dark: '#003730',
},
secondary: {
main: '#6A5F00',
},
},
typography: {
fontFamily: 'Roboto, sans-serif',
h1: { fontSize: '2.5rem', fontWeight: 700 },
body1: { fontSize: '1rem', lineHeight: 1.5 },
},
components: {
MuiButton: {
defaultProps: {
disableElevation: true,
},
styleOverrides: {
root: {
borderRadius: 8,
textTransform: 'none',
},
},
},
MuiCard: {
: {
: {
: ,
: ,
},
},
},
},
});
theme;
{ } ;
;
theme ;
;
() {
(
);
}
;
❌ BAD: Mutating Theme Directly
Never modify the theme object outside of createTheme.
import theme from './theme';
theme.palette.primary.main = '#FF0000';
3. Styling
Prioritize the sx prop for simple, one-off overrides and the styled API for reusable, complex styles.
3.1 sx Prop for Quick Overrides
Use sx for component-specific, ad-hoc styling that leverages theme tokens and provides responsive capabilities.
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
function MyComponent() {
return (
<Box sx={{ p: { xs: 2, sm: 3 }, display: 'flex', gap: 2 }}>
<Button
variant="contained"
sx={{
mt: 2, // margin-top from theme spacing
bgcolor: 'primary.dark', // background color from theme palette
'&:hover': {
bgcolor: 'primary.main',
},
}}
>
Click Me
</Button>
</Box>
);
}
3.2 styled API for Reusable Styles
For complex, reusable styles, create dedicated styled components. This ensures consistency and maintainability.
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
export const StyledButton = styled(Button)(({ theme }) => ({
borderRadius: theme.shape.borderRadius * 2,
padding: theme.spacing(1.5, 3),
textTransform: 'none',
fontSize: '1rem',
[theme.breakpoints.down('sm')]: {
padding: theme.spacing(1, 2),
fontSize: '0.875rem',
},
}));
import { StyledButton } from './MyButton.styled';
function MyButton({ children, ...props }) {
return <StyledButton {...props}>{children}</StyledButton>;
}
;
❌ BAD: Inline Styles & Deprecated JSS
Avoid inline styles as they lack theming support and are hard to maintain. Do not use @mui/styles (JSS) as it is deprecated.
<Button style={{ marginTop: '16px', backgroundColor: '#1976d2' }}>
Submit
</Button>
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles({ });
4. Component Architecture
Favor functional components, hooks, and composition. Keep components small and focused.
4.1 Functional Components & Hooks
Always use functional components with React Hooks.
import React, { useState, useEffect } from 'react';
import { Box, Typography } from '@mui/material';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await new Promise(resolve => setTimeout(() => resolve({ id: userId, name: `User ${userId}` }), 500));
setUser(response);
};
fetchUser();
}, [userId]);
if (!user) return <Typography>Loading user...</Typography>;
return (
<Box>
<Typography variant="h4">{user.name}
{/* ... other user details */}
);
}
{
}
4.2 Small, Purpose-Driven Components
Break down complex UIs into smaller, reusable components. This improves readability, testability, and reusability.
function ProductCard({ product }) {
return (
<Card>
<CardMedia component="img" image={product.image} alt={product.name} />
<CardContent>
<Typography variant="h5">{product.name}</Typography>
<Typography variant="body2">{product.description}</Typography>
<Typography variant="h6">${product.price}</Typography>
<Rating value={product.rating} readOnly />
<Button onClick={() => console.log('Add to cart')}>Add to Cart</Button>
{/* Many other details like stock, seller info, etc. */}
</CardContent>
</Card>
);
}
function ProductImage({ src, alt }) {
return ;
}
() {
(
);
}
() {
;
}
() {
(
);
}
4.3 @mui/base for Custom Design Systems
When building a highly custom design system that doesn't adhere to Material Design, use @mui/base for unstyled components and hooks. This provides maximum flexibility.
import { useButton } from '@mui/base/useButton';
import { styled } from '@mui/system';
const CustomButtonRoot = styled('button')`
font-family: 'Inter', sans-serif;
font-weight: 600;
font-size: 0.875rem;
background-color: #3f51b5; /* Custom brand color */
padding: 12px 24px;
border-radius: 8px;
color: white;
transition: all 150ms ease;
cursor: pointer;
border: none;
&:hover {
background-color: #303f9f;
}
&:active {
background-color: #283593;
}
`;
function CustomButton(props) {
const { children, ...other } = props;
const { getRootProps } = useButton(other);
return <CustomButtonRoot {...getRootProps()}>{children}</CustomButtonRoot>;
}
5. Hooks Best Practices
Leverage React Hooks for state, effects, and performance optimizations.
5.1 useMemo & useCallback for Performance
Memoize expensive computations and function references to prevent unnecessary re-renders of child components. Only use when profiling indicates a performance bottleneck.
import React, { useState, useMemo, useCallback } from 'react';
import { List, ListItem, ListItemText } from '@mui/material';
function ItemList({ items, filterText }) {
const [count, setCount] = useState(0);
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.name.toLowerCase().includes(filterText.toLowerCase()));
}, [items, filterText]);
const handleItemClick = useCallback((itemId) => {
console.log(`Item ${itemId} clicked`);
}, []);
return (
<>
<button onClick={() => setCount(c => c + 1)}>Increment: {count}
{filteredItems.map(item => (
handleItemClick(item.id)}>
))}
);
}
5.2 React 19 useRef Compatibility
Be aware of React 19's useRef changes. For forwardRef components, ensure ref is handled correctly. MUI components handle this internally, but for custom forwardRef implementations, consider a shim for backward compatibility if supporting React < 19.
import React from 'react';
const reactMajor = parseInt(React.version.split('.')[0], 10);
export const forwardRef = <T, P = {}>(
render: React.ForwardRefRenderFunction<T, P & { ref: React.Ref<T> }>,
) => {
if (reactMajor >= 19) {
const Component = (props: any) => render(props, props.ref ?? null);
Component.displayName = render.displayName ?? render.name;
return Component as React.ForwardRefExoticComponent<P>;
}
return React.forwardRef(
render as React.ForwardRefRenderFunction<T, React.PropsWithoutRef<P>>,
);
};
= ( {