web-ui-mui-5e9c82 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-ui-mui-5e9c82 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: MUI is a pre-styled React component library built on a theme.createTheme+ThemeProviderdefine the tokens every component reads;sxstyles one instance,styled()makes a reusable one,theme.componentschanges every instance, andslots/slotPropsreach inner elements. Current: v7.x (March 2025) — CSS layers,slots/slotPropsstandardised everywhere, Grid v2 promoted toGrid, React 19 compatible. MUI X v8 covers DataGrid, DatePicker and Charts. Emotion is the styling engine; Pigment CSS is still alpha and on hold.
Detailed Resources:
styled(), custom props via shouldForwardProp, sx theme-aware, responsive and callback formssx shorthands, breakpoints, package list, v6 → v7 migration, anti-pattern code<critical_requirements>
Wrap the app in `ThemeProvider` with a `createTheme()` instance. Without one, components fall back to MUI's default theme and render correctly but wrong — no error, just someone else's design.
Import from the path, not the barrel — @mui/material/Button, not { Button } from "@mui/material". The barrel makes the dev server parse the whole package on first load, and @mui/icons-material alone is over 2000 modules.
Use `slots` and `slotProps` to reach inner elements. They are the v7 API across every component; components and componentsProps are deprecated and scheduled for removal.
Write dark-mode branches as `theme.applyStyles("dark", { … })`. It emits both rulesets and lets CSS pick, so the server and the client agree. theme.palette.mode === "dark" decides at render time, which is what makes the wrong theme flash before hydration.
</critical_requirements>
Auto-detection: MUI, Material UI, @mui/material, @mui/system, @mui/icons-material, @mui/x-data-grid, @mui/x-date-pickers, createTheme, ThemeProvider, CssBaseline, sx prop, styled, useTheme, useColorScheme, colorSchemes, cssVariables, applyStyles, slots, slotProps, shouldForwardProp, GridColDef, StyledEngineProvider, enableCssLayer
Applies to:
sx, styled(), theme.components and slots/slotProps for a given overrideHandled elsewhere:
enableCssLayer puts MUI'sstyles in a named layer so an external stylesheet can order itself against them, and how that stylesheet is written is settled elsewhere
pixel wants headless primitives instead
TextField takes error and helperText to render a failure, andinputRef to hand the underlying <input> to a library that registers by ref; what decides those values is not this skill's concern
<philosophy>
The theme is the product. Palette, typography, spacing, shadows, breakpoints, z-index and transitions are all one object, and every component reads from it. An override written at a call site is a decision that will need repeating; the same decision in theme.components is made once.
Customisation is layered, and the layer is the choice. sx for this instance, styled() for a reusable variant, theme.components for every instance, slots/slotProps for elements inside a component you do not own. Reaching for a heavier layer than the situation needs is the usual source of style that cannot be changed later.
MUI components are client components. They use context and effects, so a server-rendered app needs a cache provider above ThemeProvider and a client boundary around the pages that use them.
</philosophy>
<decision_framework>
Used in one place → sx prop
Reused, same shape each time → styled()
Every instance in the app → theme.components.MuiX
An element inside the component → slots / slotPropsOne axis, even spacing → Stack
Twelve-column grid → Grid (size prop)
Centred page with a max width → Container
Anything else that needs sx → BoxNo dark mode → one palette in createTheme
Follow the system only → cssVariables: true
User can choose → colorSchemes + useColorScheme
Both, without a flash → cssVariables: { colorSchemeSelector: "data" } + useColorSchemeUnder ~100 rows, read-only → Table
Sorting, filtering, pagination → DataGrid
Very large datasets → DataGridPro (row virtualisation)
Date or time entry → DatePicker / DateTimePicker
Charts → MUI X Charts
Hierarchy → TreeView</decision_framework>
<patterns>
const theme = createTheme({
palette: { primary: { main: "#1976d2" } },
typography: {
fontFamily: '"Inter", sans-serif',
button: { textTransform: "none" },
},
shape: { borderRadius: 8 },
spacing: 8,
});
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>;CssBaseline applies MUI's normalisation and the theme's background and text colours to <body> — without it the page around the components keeps the browser defaults.
Full code: examples/core.md
const theme = createTheme({
cssVariables: { colorSchemeSelector: "data" },
colorSchemes: {
light: { palette: { primary: { main: "#1976d2" } } },
dark: { palette: { primary: { main: "#90caf9" } } },
},
});
const { mode, setMode } = useColorScheme(); // "light" | "dark" | "system"With cssVariables, both palettes are emitted as custom properties and a data-* attribute selects between them, so the scheme is settled before React runs. mode is undefined on the first render — guard on it before rendering anything that depends on the scheme.
Full code: examples/core.md
sx prop<Box
sx={{
p: 3, // theme.spacing(3)
bgcolor: "background.paper", // theme.palette.background.paper
borderRadius: 1, // theme.shape.borderRadius
boxShadow: 3, // theme.shadows[3]
width: { xs: "100%", md: "50%" },
"&:hover": { boxShadow: 6 },
}}
/>Numbers go through the theme's scales and dotted strings resolve against the palette, so a themed value never needs to be repeated as a literal. Breakpoint objects replace media queries, and an array of sx objects merges left to right — which is how a conditional style is added without rebuilding the object.
Full code: examples/styling.md
styled()const StyledCard = styled(Card)(({ theme }) => ({
padding: theme.spacing(3),
"&:hover": { boxShadow: theme.shadows[8] },
...theme.applyStyles("dark", { backgroundColor: theme.palette.grey[900] }),
}));Reach for this when the same styling appears more than once. A second argument takes shouldForwardProp, which is what stops a styling-only prop reaching the DOM as an unknown attribute.
Full code: examples/styling.md
<Autocomplete
slots={{ paper: CustomPaper }}
slotProps={{
paper: { elevation: 8, sx: { borderRadius: 2 } },
listbox: { sx: { maxHeight: 300 } },
input: ({ open }) => ({
sx: { borderColor: open ? "primary.main" : "divider" },
}),
}}
renderInput={(params) => <TextField {...params} label="Framework" />}
/>slots replaces an inner component, slotProps configures one, and a slotProp written as a callback receives that slot's own state. Define slot components outside the render — an inline arrow is a new component type each time, which remounts the slot on every render.
Full code: examples/form-inputs.md
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 4 }}><Sidebar /></Grid>
<Grid size={{ xs: 12, md: 8 }}><MainContent /></Grid>
</Grid>
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
<Button variant="contained">Save</Button>
</Stack>
<Container maxWidth="lg" sx={{ py: 4 }}>{children}</Container>In v7 Grid is the former Grid2 and takes one size prop instead of separate xs/sm/md props; the previous component is still available as GridLegacy.
Full code: examples/layout.md
declare module "@mui/material/styles" {
interface Palette {
neutral: Palette["primary"];
}
interface PaletteOptions {
neutral?: PaletteOptions["primary"];
}
}
declare module "@mui/material/Button" {
interface ButtonPropsColorOverrides {
neutral: true;
}
}Two augmentations, and both are needed: the first makes the token exist on the theme, the second lets a component accept it as a prop value. Skip the second and color="neutral" is a type error at every call site even though the theme carries it.
Full code: examples/core.md
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
<GlobalStyles styles="@layer theme, base, mui, components, utilities;" />
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</AppRouterCacheProvider>Emotion generates styles as components render, so a server-rendered app needs a cache provider above ThemeProvider to collect and flush them with the streamed HTML — without it the markup arrives unstyled and restyles on hydration. MUI publishes an adapter package per supported server framework; a client-only app uses StyledEngineProvider in the same position instead. enableCssLayer puts MUI's output into a named layer, and the @layer declaration is what fixes the order of that layer against everything else on the page.
Full code, both arrangements: examples/core.md
</patterns>
<performance>
Path imports. import Button from "@mui/material/Button" skips barrel parsing; the barrel form costs several times the dev-server startup. Some bundlers and frameworks rewrite barrel imports automatically — check before assuming the cost applies. A lint rule restricting the pattern ^@mui/[^/]+$ is what keeps it from creeping back.
Stable references for anything a component treats as identity. DataGrid columns, slot components, and Dialog TransitionProps are all compared by reference: define them at module scope, or memoise them. Inline, they cause a re-render or a remount on every parent render.
`createTheme` does not affect bundle size — it is data, not components. The bundle is decided by which components are imported.
</performance>
<red_flags>
Breaks at runtime:
theme.palette.mode === "dark" in a style function — resolved at render time, so the server andthe first client paint disagree and the wrong theme flashes
slots — a new component type each render, so the slot remounts and loses itsstate and focus
columns on a DataGrid — a new array each render, re-rendering the whole grid@mui/material/styles/createTheme — the v7 ESM package layoutdoes not expose those paths
@mui/lab imports for Alert, Skeleton or Autocomplete — they graduated to @mui/material in v7and the boundary belongs on the page, not only on the root layout
Surprising behaviour:
spacing is a multiplier, not pixels: spacing(2) is 16px at the default base of 8sx arrays merge left to right, so a later entry wins — which is what makes conditional styles workTextField is three components in a trench coat (input, label, helper text), so targeting theactual <input> means slotProps.input
zIndex by hand fights MUI's own scale, where modal, drawer, snackbar and tooltip alreadyhave assigned values
useMediaQuery returns false during server rendering, so a desktop-first branch renders thewrong thing before hydration
useColorScheme().mode is undefined on the first render, and rendering a scheme-dependent iconfrom it produces a hydration mismatch
not at the theme
components and componentsProps still work in v7 but are deprecated</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.