From frontend-pipeline
Use this skill for ANY React component work in MUI-based projects — building, styling, fixing, debugging, or refactoring. Covers MUI (Material UI) v7, MUI X v8 (DataGrid, DatePicker, Autocomplete), TanStack Query v5, React Hook Form + Zod, and React Router v7. Triggers on: 'create a form', 'build a data table', 'add server-side pagination', 'create a dialog', 'configure MUI theme', 'use the sx prop', 'add a snackbar', 'create tabs', 'build a page layout', 'fix the grid layout', 'the component doesn't align', useQuery, useMutation, or Italian: 'crea un componente React', 'aggiungi una tabella dati', 'crea una pagina', 'sistema il layout'. Also activates for Grid layout, responsive design, WCAG accessibility, component decomposition, and any task involving MUI imports or the sx prop — even if the user doesn't explicitly mention MUI.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-pipeline:mui-react-developmentThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Best practices and patterns for building React applications with MUI (Material UI) v7, MUI X v8, TanStack Query v5, React Hook Form + Zod, and React Router v7.
Best practices and patterns for building React applications with MUI (Material UI) v7, MUI X v8, TanStack Query v5, React Hook Form + Zod, and React Router v7.
MUI v7 unified the Grid API. The old Grid2 and Unstable_Grid2 no longer exist.
// CORRECT - MUI v7
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>Content</Grid>
</Grid>
// WRONG - removed in v7
<Grid item xs={12} md={6}>Content</Grid>
<Grid2 size={{ xs: 12 }}>Content</Grid2>
For responsive patterns, auto-sizing, and v6->v7 migration table, see
references/mui-v7-patterns.md
Always prefer sx prop for styling. It integrates with the theme and supports responsive values.
// CORRECT
<Box sx={{ p: { xs: 2, md: 4 }, bgcolor: 'background.paper' }}>
// WRONG - never use inline styles
<Box style={{ padding: '16px', backgroundColor: '#fff' }}>
Use theme values, never hardcode colors, spacing, or typography.
// CORRECT - theme-aware
sx={{ color: 'primary.main', borderRadius: 1, p: 2 }}
// WRONG - hardcoded
sx={{ color: '#673ab7', borderRadius: '4px', padding: '16px' }}
MUI v7 replaced all capitalized *Props with the unified slotProps API. This applies across all MUI components, not just TextField.
// CORRECT - MUI v7 slot pattern
<TextField
slotProps={{
input: { 'aria-label': 'Search' },
inputLabel: { shrink: true }
}}
/>
// WRONG - deprecated in MUI v7
<TextField
InputProps={{ 'aria-label': 'Search' }}
InputLabelProps={{ shrink: true }}
/>
// Autocomplete — slotProps for listbox, paper, popper
<Autocomplete
options={options}
slotProps={{
listbox: { sx: { maxHeight: 300 } },
paper: { elevation: 4 },
popper: { placement: 'bottom-start' }
}}
renderInput={(params) => <TextField {...params} label="Search" />}
/>
// Select — slotProps for the menu
<Select
value={value}
onChange={handleChange}
slotProps={{
input: { 'aria-label': 'Choose option' }
}}
MenuProps={{ PaperProps: { sx: { maxHeight: 300 } } }}
>
<MenuItem value="a">Option A</MenuItem>
</Select>
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
import { itIT } from '@mui/x-data-grid/locales'; // FacileGestire uses Italian locale
<DataGrid
rows={rows}
columns={columns}
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
pageSizeOptions={[10, 25, 50]}
localeText={itIT.components.MuiDataGrid.defaultProps.localeText} // Adapt locale to project
/>
For server-side pagination, custom column renderers, and toolbar patterns, see
references/mui-v7-patterns.md
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker label="Select date" value={value} onChange={setValue} />
</LocalizationProvider>
Note: This is a FacileGestire project convention, not a universal MUI rule. Check the target project's CLAUDE.md for its own component size limits.
Every component file stays under 150 lines. When approaching this limit, decompose into a folder structure:
ComponentName/
├── index.tsx (5 LOC) - Export only
├── ComponentName.tsx (<100) - Orchestration
├── ComponentNameHeader.tsx (<80) - UI section
├── ComponentNameContent.tsx(<80) - UI section
├── useComponentName.ts (<80) - Business logic hook
└── types.ts (<50) - Interfaces
import { type FC } from 'react';
import { Box, Typography } from '@mui/material';
interface ComponentNameProps {
title: string;
onAction?: () => void;
}
export const ComponentName: FC<ComponentNameProps> = ({ title, onAction }) => {
return (
<Box component="section" aria-label={title} sx={{ p: { xs: 2, sm: 3, md: 4 } }}>
<Typography variant="h5" component="h2">{title}</Typography>
</Box>
);
};
Key points: semantic HTML (component="section"), aria-label on containers, responsive sx values, typed props interface.
Every component must include:
aria-label or aria-labelledby on interactive containerssection, nav, main, header)React 19 introduces the React Compiler which auto-memoizes components, values, and callbacks. If the target project uses React 19 with the compiler enabled, manual useMemo, useCallback, and memo are unnecessary — the compiler handles it. Only add manual memoization when targeting React 18 or when the compiler is not enabled.
Use notistack (already integrated in FacileGestire projects) or MUI's Snackbar for user feedback after CRUD operations:
import { useSnackbar } from 'notistack';
const { enqueueSnackbar } = useSnackbar();
// After mutation success
onSuccess: () => {
enqueueSnackbar('Record saved successfully', { variant: 'success' });
queryClient.invalidateQueries({ queryKey: ['items'] });
},
onError: () => {
enqueueSnackbar('Failed to save record', { variant: 'error' });
},
Autocomplete requires Controller because it manages complex value/onChange behavior:
<Controller
name="supplier"
control={control}
render={({ field: { onChange, value } }) => (
<Autocomplete
options={suppliers}
getOptionLabel={(opt) => opt.name}
value={suppliers.find((s) => s.id === value) ?? null}
onChange={(_, newValue) => onChange(newValue?.id ?? null)}
renderInput={(params) => (
<TextField {...params} label="Supplier" error={!!errors.supplier} helperText={errors.supplier?.message} />
)}
/>
)}
/>
import { Tabs, Tab, Box } from '@mui/material';
const [tabIndex, setTabIndex] = useState(0);
<Tabs value={tabIndex} onChange={(_, v) => setTabIndex(v)} aria-label="Section tabs">
<Tab label="Details" id="tab-0" aria-controls="tabpanel-0" />
<Tab label="History" id="tab-1" aria-controls="tabpanel-1" />
</Tabs>
<Box role="tabpanel" id="tabpanel-0" hidden={tabIndex !== 0} aria-labelledby="tab-0">
{tabIndex === 0 && <DetailsContent />}
</Box>
<Box role="tabpanel" id="tabpanel-1" hidden={tabIndex !== 1} aria-labelledby="tab-1">
{tabIndex === 1 && <HistoryContent />}
</Box>
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, error } = useQuery({
queryKey: ['users', filters],
queryFn: () => apiClient.get('/api/v1/users', { params: filters }),
staleTime: 5 * 60 * 1000,
});
import { useMutation, useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: CreateUserData) => apiClient.post('/api/v1/users', data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
Key rules:
useState for server data - always use useQueryqueryKey arrays for cache managementstaleTime based on data freshness needsFor error handling, optimistic updates, dependent queries, and prefetching patterns, see
references/react-stack-patterns.md
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Required'),
email: z.string().email('Invalid email'),
});
type FormData = z.infer<typeof schema>;
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
Use register for simple text inputs. Use Controller for MUI components that need value/onChange control (Select, Switch, DatePicker, Autocomplete).
// Simple text input - register is sufficient
<TextField
{...register('name')}
error={!!errors.name}
helperText={errors.name?.message}
label="Name"
fullWidth
/>
// Complex MUI component - use Controller
<Controller
name="role"
control={control}
render={({ field }) => (
<TextField {...field} select label="Role" error={!!errors.role}>
<MenuItem value="admin">Admin</MenuItem>
<MenuItem value="user">User</MenuItem>
</TextField>
)}
/>
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Dashboard /> },
{ path: 'users', element: <Users /> },
{ path: 'users/:id', element: <UserDetail /> },
],
},
]);
import { useNavigate, useParams } from 'react-router-dom';
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
The MUI MCP server (@mui/mcp) provides direct access to official MUI documentation and API references. It should be installed and running for optimal MUI development support.
Installation:
npx -y @mui/mcp@latest
MCP Configuration (add to .mcp.json or project MCP settings):
{
"mcpServers": {
"mui-mcp": {
"command": "npx",
"args": ["-y", "@mui/mcp@latest"]
}
}
}
If the MUI MCP server is unavailable, inform the user that documentation lookup is limited and provide the installation configuration above. Proceed using the patterns documented in this skill and reference files as fallback.
Before writing MUI component code, fetch the MUI documentation index to identify the relevant documentation pages:
https://mui.com/material-ui/llms.txtWebFetch to retrieve the index and locate the specific component/API documentation pageWebFetch is unavailable or the request fails, skip to Step 2 and use the MUI MCP tool directlyUse the documentation page URLs found in the index (Step 1) as input to mcp__mui-mcp__useMuiDocs for precise API verification.
Important: Read the target project's package.json to determine exact MUI package versions before querying docs. Use the installed version (e.g., @mui/[email protected]) in the docs URL, not hardcoded version numbers, to ensure documentation matches the project.
For non-MUI libraries in the stack (TanStack Query, Zod, React Router), use Context7 MCP: mcp__plugin_context7_context7__resolve-library-id + query-docs.
For detailed patterns beyond the essentials above, consult these reference files:
references/mui-v7-patterns.md - Consult when implementing Grid layouts, theme customization, dark mode, responsive drawers, DataGrid with server-side pagination, custom column renderers, toolbar patterns, or Dialog/Card/Loading component patternsreferences/react-stack-patterns.md - Consult when implementing optimistic updates, dependent queries, prefetching, complex form validation (conditional/nested/useFieldArray), protected routes, breadcrumbs, lazy loading, error boundaries, or state management decisions. File includes a table of contents for quick navigationGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub dotcreativeagency/claude-plugins --plugin frontend-pipeline