This file provides guidance for AI agents working with code in this repository.
Only pnpm is supported (yarn/npm will fail). Use the -F flag for workspace operations:
pnpm -F @mui/material add some-package # Add dependency to a package
pnpm -F @mui/material build # Build a specific packageNever use cd to navigate into package directories for commands.
pnpm install # Install deps if necessary
pnpm docs:dev # Start docs dev server onlypnpm release:build # Build all packages (except docs)
pnpm docs:build # Build documentation sitepnpm test:unit # Run all unit tests (jsdom)
pnpm test:unit ComponentName # Run tests matching pattern
pnpm test:unit -t "test name" # Grep for specific test name
pnpm test:browser # Run tests in real browsers (Chrome, Firefox, WebKit)
pnpm test:e2e # End-to-end tests
pnpm test:regressions # Visual regression testspnpm prettier # Format staged changes
pnpm eslint # Lint with cache
pnpm typescript # Type check all packagesAfter changing component props or TypeScript declarations:
pnpm proptypes && pnpm docs:apiAlways author the TypeScript version of the demos. To generate the JavaScript variant, run:
pnpm docs:typescript:formattedThis is a monorepo managed by Lerna with Nx for caching. Key packages:
@mui/material- Core Material UI components@mui/system- Styling system (sx prop, styled, theme)@mui/lab- Experimental components (new components go here first)@mui/icons-material- Material Design icons@mui/utils- Internal utilities@mui/styled-engine- CSS-in-JS abstraction (Emotion by default)
Internal packages (not published): @mui-internal/*, @mui/internal-*
- Use
interface(nottype) for component props - Export
{ComponentName}Propsinterface from component files - Path aliases available:
@mui/material→./packages/mui-material/src
These guidelines only apply for errors thrown from public packages.
Every error message must:
- Say what happened - Describe the problem clearly
- Say why it's a problem - Explain the consequence
- Point toward how to solve it - Give actionable guidance
Format:
- Prefix with
MUI: - Use string concatenation for readability
- Include a documentation link when applicable (
https://mui.com/r/...)
Use the /* minify-error */ comment to activate the babel plugin:
throw /* minify-error */ new Error(
'MUI: Expected valid input target. ' +
'Did you use a custom `inputComponent` and forget to forward refs? ' +
'See https://mui.com/r/input-component-ref-interface for more info.',
);The minifier works with both Error and TypeError constructors.
Run pnpm extract-error-codes to update docs/public/static/error-codes.json.
Important: If the update created a new error code, but the new and original message have the same number of arguments and semantics haven't changed, update the original error in error-codes.json instead of creating a new code.
packages/mui-material/src/Button/
├── Button.tsx # Component implementation
├── Button.d.ts # TypeScript declarations (for JSDoc API docs)
├── Button.test.js # Unit tests
├── buttonClasses.ts # CSS classes
└── index.ts # Public exports
- Use
createRenderer()from@mui/internal-test-utils - Use Chai BDD-style assertions (
expect(x).to.equal(y)) - Custom matchers:
toErrorDev(),toWarnDev()for console assertions - Prefer testing components with full interactions using
user.*methods. AvoidfireEventandsetPropsif possible. - If tests require the browser because, for example, they require layout measurements, restrict it to the Chromium env by using
it.skipIf(isJsdom())ordescribe.skipIf(isJsdom())(search other tests for example usage if unsure).
import { createRenderer } from '@mui/internal-test-utils';
describe('Button', () => {
const { render } = createRenderer();
it('renders children', async () => {
const handleClick = vi.fn();
const { getByRole, user } = render(<Button onClick={handleClick}>Hello</Button>);
const button = getByRole('button');
expect(button).to.have.text('Hello');
await user.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
});axe-core runs inside the visual-regression Playwright loop (test/regressions/index.test.js) — no separate browser session. Screenshots and a11y are independent: a demo can opt out of one and still run the other.
Key files:
test/regressions/demoMeta.ts—SCREENSHOT_RULESandA11Y_RULESarrays, matched last-wins (no inheritance: overrides restate every field) againstdocs/data/material/components/{slug}/{Demo}(minimatch globs).test/regressions/a11y/axe.ts— assertscolor-contrastandlink-in-text-blockunless listed inskipAssertions.test/regressions/a11y/a11yReporter.ts— writes one file per slug atdocs/data/material/components/{slug}/{slug}.a11y.json. Each file is keyed by demo name, then by axe rule ID. Each rule records astatus(pass,fail, orincomplete) and WCAG tags.
Enroll a component (slug-wide, or narrow with brace-glob):
// test/regressions/demoMeta.ts
{ test: 'docs/data/material/components/alert/*', enabled: true, skipAssertions: ['color-contrast'] },
{ test: 'docs/data/material/components/buttons/{BasicButtons,ColorButtons}', enabled: true },Override a specific demo: append a per-demo rule after the slug-wide rule (last-match-wins; the override must restate every field it wants):
{ test: 'docs/data/material/components/popover/AnchorPlayground', enabled: false }, // Redux isolationRun pnpm test:regressions to refresh the *.a11y.json files. CI fails if any are stale.
For local iteration, scope the run with vitest's -t test-name filter (matched against the it() strings, which contain the route). Non-matching tests are skipped — their bodies don't execute, so the browser never navigates to those routes.
# in one terminal
pnpm test:regressions:server
# in another — note no `--`, pnpm forwards args directly
pnpm test:regressions:run -t '/docs-components-buttons/' # one slug
pnpm test:regressions:run -t '/docs-components-buttons/BasicButtons$' # one demo
pnpm test:regressions:run -t '/docs-components-(buttons|chips)/' # multiple slugsFiltered runs only refresh the matched slugs' *.a11y.json. Run the unfiltered pnpm test:regressions before pushing.
Use one-level deep imports to avoid bundling entire packages:
import Button from '@mui/material/Button'; // Good
import { Button } from '@mui/material'; // Avoid in packagesPackaged guidance for common integration topics lives under skills/. Each skill is a self-contained directory:
| Skill | Focus |
|---|---|
| skills/material-ui-styling | sx, styled(), theme overrides, slots, global CSS |
| skills/material-ui-theming | createTheme, design tokens, colorSchemes, CSS variables |
| skills/material-ui-nextjs | App/Pages Router, Emotion cache, next/font, Link, SSR |
| skills/material-ui-tailwind | Tailwind v4 @layer, enableCssLayer, v3 interop |
Read the relevant AGENTS.md when helping users with those topics.
pnpm prettier- Format codepnpm eslint- Pass lintingpnpm typescript- Pass type checkingpnpm test:unit- Pass unit tests- If API changed:
pnpm proptypes && pnpm docs:api - If demos changed:
pnpm docs:typescript:formatted - If
.mdfiles changed:pnpm vale <file1> <file2> ...- Check prose style and grammar
[component] Imperative description
Examples:
[button] Add loading state[docs] Fix typo in Grid documentation