--- name: mantine-react-component-dev description: Development guidelines and patterns for building React component libraries with Mantine UI, TypeScript, and modern tooling. Use this skill when creating, editing, or maintaining Mantine-based React components, implementing Styles API patterns, managing component composition, or working with TypeScript-safe component factories. --- # Mantine React Component Development Skill ## When to Use This Skill Apply this skill when working on: - **Component Development**: Creating or editing React components built on Mantine's factory pattern - **TypeScript Patterns**: Implementing polymorphic components, type-safe props, and CSS variables - **Styles API**: Configuring component styling through Mantine's Styles API system (selectors, vars, modifiers) - **Component Composition**: Building compound components with static sub-components (e.g., `Component.Target`) - **Context Management**: Implementing safe context patterns for component state sharing - **Accessibility**: Ensuring ARIA compliance, keyboard navigation, and focus management - **Code Review**: Maintaining consistency with established patterns and conventions - **Documentation**: Writing component demos, MDX docs, and API references ## Project Structure Components are organized in a monorepo workspace structure: - **`/package/src/`**: Main component source code - `ComponentName.tsx`: Main component implementation - `ComponentName.module.css`: Component-scoped styles - `ComponentName.context.ts`: Context providers (if needed) - `ComponentName.errors.ts`: Error messages - `ComponentName.test.tsx`: Jest + Testing Library tests - `ComponentName.story.tsx`: Storybook stories - `SubComponent/SubComponent.tsx`: Sub-components in their own folders - `index.ts`: Public exports - **`/docs/`**: Next.js documentation site - `demos/ComponentName.demo.*.tsx`: Interactive demos - `styles-api/ComponentName.styles-api.ts`: Styles API metadata - `docs.mdx`: Main documentation page Refer to existing components in [`./package/src/`](./package/src/) for implementation examples. ## TypeScript Patterns ### Component Factory Pattern All components use Mantine's polymorphic factory pattern. ```typescript import { polymorphicFactory, PolymorphicFactory, useProps, useStyles, createVarsResolver } from '@mantine/core'; export type ComponentStylesNames = 'root' | 'element'; export type ComponentCssVariables = { root: '--custom-var' | '--another-var'; }; export interface ComponentProps extends BoxProps, StylesApiProps { /** Prop description */ customProp?: string; } export type ComponentFactory = PolymorphicFactory<{ props: ComponentProps; defaultComponent: 'div'; defaultRef: HTMLDivElement; stylesNames: ComponentStylesNames; vars: ComponentCssVariables; staticComponents: { SubComponent: typeof SubComponent; }; }>; const defaultProps: Partial = { customProp: 'default', }; const varsResolver = createVarsResolver((_, { customProp }) => ({ root: { '--custom-var': customProp, }, })); export const Component = polymorphicFactory((_props, ref) => { const props = useProps('Component', defaultProps, _props); const { classNames, className, style, styles, unstyled, vars, customProp, ...others } = props; const getStyles = useStyles({ name: 'Component', classes, props, className, style, classNames, styles, unstyled, vars, varsResolver, }); return ( {/* component content */} ); }); Component.displayName = '@your-scope/Component'; Component.SubComponent = SubComponent; ``` **Key Requirements:** - Use `unknown` for unconstrained generics; narrow with type guards - Avoid `any`; use `React.ReactNode` for children - Define explicit type aliases for style names and CSS variables - Use `useProps` hook for default prop merging - Implement `varsResolver` for CSS custom properties ### Context Pattern For components requiring state sharing, use Mantine's safe context pattern. ```typescript import { createSafeContext } from '@mantine/core'; import { COMPONENT_ERRORS } from './Component.errors'; interface ComponentContext { state: boolean; setState: (value: boolean) => void; } export const [ComponentContextProvider, useComponentContext] = createSafeContext( COMPONENT_ERRORS.context ); ``` **Error Definitions** in `Component.errors.ts`: ```typescript export const COMPONENT_ERRORS = { context: 'Component was not found in the tree', validation: 'Specific validation message', }; ``` ### Sub-Component Pattern Sub-components access parent context and enforce constraints. ```typescript import { forwardRef, useProps, isElement, createEventHandler } from '@mantine/core'; import { useComponentContext } from '../Component.context'; export interface SubComponentProps { children: React.ReactNode; refProp?: string; } export const SubComponent = forwardRef((props, ref) => { const { children, ...others } = useProps('SubComponent', {}, props); if (!isElement(children)) { throw new Error(COMPONENT_ERRORS.children); } const ctx = useComponentContext(); const onClick = createEventHandler(children.props.onClick, () => ctx.toggle()); return cloneElement(children, { onClick, ref }); }); ``` ## Styles API Every component exposes a Styles API for customization. Define in [`./docs/styles-api/Component.styles-api.ts`](./docs/styles-api/): ```typescript import type { ComponentFactory } from '@your-scope/component'; import type { StylesApiData } from '../components/styles-api.types'; export const ComponentStylesApi: StylesApiData = { selectors: { root: 'Root element', element: 'Specific child element', }, vars: { root: { '--custom-var': 'Controls custom behavior', '--another-var': 'Controls another aspect', }, }, modifiers: [ { modifier: 'data-active', selector: 'root', condition: '`active` prop is set' } ], }; ``` **CSS Module** (`Component.module.css`): ```css .root { /* Use CSS custom properties from varsResolver */ property: var(--custom-var, fallback); } .element { /* Scoped class name */ } /* Data attribute modifiers */ .root[data-active] { /* Active state */ } ``` ## Component Guidelines ### Controlled vs Uncontrolled State Use `@mantine/hooks` `useUncontrolled` for dual-mode state: ```typescript import { useUncontrolled } from '@mantine/hooks'; const [value, setValue] = useUncontrolled({ value: props.value, defaultValue: props.defaultValue, finalValue: undefined, onChange: props.onChange, }); ``` ### Lifecycle Hooks Use `useDidUpdate` from `@mantine/hooks` for effect-on-update patterns: ```typescript import { useDidUpdate } from '@mantine/hooks'; useDidUpdate(() => { props.onStateChange?.(state); }, [state]); ``` ### Props Validation Validate props at runtime when type system isn't enough: ```typescript if (React.Children.count(children) !== 2) { throw new Error('Component requires exactly two children'); } ``` ### Ref Forwarding Always support ref forwarding for component composition: ```typescript export const Component = polymorphicFactory((_props, ref) => { return ; }); ``` ## Coding Standards ### Import Order Prettier auto-sorts imports per [`./.prettierrc.mjs`](./.prettierrc.mjs): 1. CSS imports 2. React 3. Next.js (if applicable) 4. Built-in modules 5. Third-party modules 6. `@mantine/*` packages 7. Local imports (parent then sibling) 8. CSS modules last ### Formatting - **Print Width**: 100 characters - **Quotes**: Single quotes - **Trailing Commas**: ES5-style - **MDX**: 70 character print width Run `npm run prettier:write` before committing. ### Linting ESLint config extends [`eslint-config-mantine`](./eslint.config.mjs): ```javascript import mantine from 'eslint-config-mantine'; import tseslint from 'typescript-eslint'; export default tseslint.config(...mantine, { ignores: ['**/.next/**', '**/*.{mjs,cjs,js,d.ts,d.mts}'] }); ``` Run `npm run lint` to check all rules. ### TypeScript Configuration Primary config ([`./tsconfig.json`](./tsconfig.json)): - **Target**: ES2015 - **Module**: ESNext with Node resolution - **JSX**: React (classic runtime) - **Strict**: Enabled (via mantine config) - **Skip Lib Check**: true (for faster builds) **Build config** (`tsconfig.build.json`) isolates compilation scope. ## Testing Use `@mantine-tests/core` renderer with Testing Library. ```typescript import React from 'react'; import { render } from '@mantine-tests/core'; import { Component } from './Component'; describe('Component', () => { it('renders without crashing', () => { const { container } = render(Content); expect(container).toBeTruthy(); }); it('applies custom className', () => { const { container } = render(); expect(container.querySelector('.custom')).toBeTruthy(); }); it('forwards ref', () => { const ref = React.createRef(); render(); expect(ref.current).toBeTruthy(); }); }); ``` Run tests with `npm run jest`. ## Documentation ### Demos Create interactive demos in [`./docs/demos/`](./docs/demos/): ```typescript // Component.demo.basic.tsx import { Component } from '@your-scope/component'; import { MantineDemo } from '@docs/components'; const code = ` import { Component } from '@your-scope/component'; function Demo() { return Content; } `; export const basic: MantineDemo = { type: 'code', component: Demo, code, }; ``` **Configurator demos** use `MantineDemo` type `'configurator'` with `controls` object. ### MDX Pages Main docs page at [`./docs/docs.mdx`](./docs/docs.mdx): ```mdx import { InstallScript } from './components/InstallScript/InstallScript'; import * as demos from './demos'; ## Installation ## Usage ## Props ## Styles API ``` Refer to existing documentation structure in `/docs` for consistency. ## Accessibility ### ARIA Patterns - Use semantic HTML elements (`