
mantine-react-component-dev
by gfazioli
A highly customizable LED indicator component for React applications built with Mantine. Provides visual feedback for status indicators with support for colors, sizes, animations, and controlled states.
SKILL.md
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 codeComponentName.tsx: Main component implementationComponentName.module.css: Component-scoped stylesComponentName.context.ts: Context providers (if needed)ComponentName.errors.ts: Error messagesComponentName.test.tsx: Jest + Testing Library testsComponentName.story.tsx: Storybook storiesSubComponent/SubComponent.tsx: Sub-components in their own foldersindex.ts: Public exports
-
/docs/: Next.js documentation sitedemos/ComponentName.demo.*.tsx: Interactive demosstyles-api/ComponentName.styles-api.ts: Styles API metadatadocs.mdx: Main documentation page
Refer to existing components in ./package/src/ for implementation examples.
TypeScript Patterns
Component Factory Pattern
All components use Mantine's polymorphic factory pattern.
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<ComponentFactory> {
/** 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<ComponentProps> = {
customProp: 'default',
};
const varsResolver = createVarsResolver<ComponentFactory>((_, { customProp }) => ({
root: {
'--custom-var': customProp,
},
}));
export const Component = polymorphicFactory<ComponentFactory>((_props, ref) => {
const props = useProps('Component', defaultProps, _props);
const { classNames, className, style, styles, unstyled, vars, customProp, ...others } = props;
const getStyles = useStyles<ComponentFactory>({
name: 'Component',
classes,
props,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver,
});
return (
<Box ref={ref} {...getStyles('root')} {...others}>
{/* component content */}
</Box>
);
});
Component.displayName = '@your-scope/Component';
Component.SubComponent = SubComponent;
Key Requirements:
- Use
unknownfor unconstrained generics; narrow with type guards - Avoid
any; useReact.ReactNodefor children - Define explicit type aliases for style names and CSS variables
- Use
usePropshook for default prop merging - Implement
varsResolverfor CSS custom properties
Context Pattern
For components requiring state sharing, use Mantine's safe context pattern.
import { createSafeContext } from '@mantine/core';
import { COMPONENT_ERRORS } from './Component.errors';
interface ComponentContext {
state: boolean;
setState: (value: boolean) => void;
}
export const [ComponentContextProvider, useComponentContext] = createSafeContext<ComponentContext>(
COMPONENT_ERRORS.context
);
Error Definitions in Component.errors.ts:
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.
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<HTMLElement, SubComponentProps>((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:
import type { ComponentFactory } from '@your-scope/component';
import type { StylesApiData } from '../components/styles-api.types';
export const ComponentStylesApi: StylesApiData<ComponentFactory> = {
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):
.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:
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:
import { useDidUpdate } from '@mantine/hooks';
useDidUpdate(() => {
props.onStateChange?.(state);
}, [state]);
Props Validation
Validate props at runtime when type system isn't enough:
if (React.Children.count(children) !== 2) {
throw new Error('Component requires exactly two children');
}
Ref Forwarding
Always support ref forwarding for component composition:
export const Component = polymorphicFactory<ComponentFactory>((_props, ref) => {
return <Box ref={ref} {...others} />;
});
Coding Standards
Import Order
Prettier auto-sorts imports per ./.prettierrc.mjs:
- CSS imports
- React
- Next.js (if applicable)
- Built-in modules
- Third-party modules
@mantine/*packages- Local imports (parent then sibling)
- 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.
CSS Coding Standards
Naming Conventions:
- Use kebab-case for all CSS identifiers:
- Class names:
.component-name,.element-class - Custom properties:
--variable-name,--led-color - Keyframe names:
@keyframes slide-in,@keyframes led-pulse
- Class names:
- Never use camelCase or PascalCase in CSS (e.g.,
ledPulseis invalid, useled-pulse)
Property Order:
- Avoid using CSS shorthand properties (like
background) after their longhand equivalents (likebackground-color) - This causes the shorthand to override the longhand, leading to lint errors
- Example of what NOT to do:
.element { background-color: red; /* This gets overridden */ background: blue; /* ERROR: shorthand overrides longhand */ } - Instead, use only shorthand OR only longhand:
.element { background: blue; /* Correct: use shorthand only */ }
Validation:
- Run
npm run lintto catch CSS linting errors - stylelint enforces:
declaration-block-no-shorthand-property-overrides: Prevents shorthand/longhand conflictskeyframes-name-pattern: Enforces kebab-case for animation names
Linting
ESLint config extends eslint-config-mantine:
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):
- 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.
import React from 'react';
import { render } from '@mantine-tests/core';
import { Component } from './Component';
describe('Component', () => {
it('renders without crashing', () => {
const { container } = render(<Component>Content</Component>);
expect(container).toBeTruthy();
});
it('applies custom className', () => {
const { container } = render(<Component className="custom" />);
expect(container.querySelector('.custom')).toBeTruthy();
});
it('forwards ref', () => {
const ref = React.createRef<HTMLDivElement>();
render(<Component ref={ref} />);
expect(ref.current).toBeTruthy();
});
});
Run tests with npm run jest.
Documentation
Demos
Create interactive demos in ./docs/demos/:
// 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 <Component>Content</Component>;
}
`;
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:
import { InstallScript } from './components/InstallScript/InstallScript';
import * as demos from './demos';
## Installation
<InstallScript packages="@your-scope/component" />
## Usage
<Demo data={demos.basic} />
## Props
<PropsTable component="Component" />
## Styles API
<StylesApiTable component="Component" />
Refer to existing documentation structure in /docs for consistency.
Accessibility
ARIA Patterns
- Use semantic HTML elements (
<button>,<nav>, etc.) over<div>with roles - Add ARIA attributes only when semantic HTML is insufficient
- Reference MDN ARIA practices: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
Keyboard Navigation
- Ensure all interactive elements are keyboard-accessible
- Use
tabIndex={-1}for programmatic focus management - Implement arrow-key navigation for composite widgets
Focus Management
import { useFocusTrap } from '@mantine/hooks';
const focusTrapRef = useFocusTrap(active);
Data Attributes for State
Expose component state via data attributes for CSS and screen readers:
<div data-active={isActive} data-disabled={disabled} />
Security and Maintenance
No Hardcoded Secrets
- Never commit API keys, tokens, or credentials
- Use environment variables for sensitive configuration
- Exclude
.envfiles in.gitignore
Dependency Hygiene
- Keep
@mantine/coreand@mantine/hooksversions in sync (see./package/package.json) - Use
peerDependenciesfor React and Mantine packages - Run
npm run syncpackto verify version consistency
Least Privilege
- Minimize scope of React Context providers
- Avoid global state; prefer component-level state
- Use
createSafeContextto enforce provider boundaries
Edge Cases and Troubleshooting
Module Resolution
- Ensure all imports use relative paths for local modules
- Check
package.jsonexportsfield for correct entry points - Verify
tsconfig.jsonpathsif using aliases
CSS Modules
- Class names are auto-scoped via
hash-css-selector(see./rollup.config.mjs) - Import CSS modules as
import classes from './Component.module.css' - Use
getStyleshelper for className merging
Polymorphic Components
- Default component is
div; override withcomponentprop - Ref type must match
defaultRefin factory definition - Props are merged:
BoxProps & ComponentProps & { component?: any }
Testing Library Queries
- Prefer
getByRoleovergetByTestId - Use
screenfor global queries orcontainerfor scoped - Mock
window.matchMediaif needed (see./jsdom.mocks.cjs)
Storybook Integration
- Stories go in
Component.story.tsxalongside component - Use CSF3 format with
MetaandStoryObjtypes - Stories auto-generate docs from TypeScript types
File Organization
Maintain consistent file structure per component:
package/src/
├── Component.tsx # Main component
├── Component.module.css # Scoped styles
├── Component.context.ts # Context (if needed)
├── Component.errors.ts # Error constants
├── Component.test.tsx # Tests
├── Component.story.tsx # Storybook
├── SubComponent/
│ └── SubComponent.tsx # Sub-component
└── index.ts # Public API
Public API (index.ts):
export { Component } from './Component';
export type { ComponentProps, ComponentFactory } from './Component';
Commit Hygiene
- Run
npm run testbefore committing (includes prettier, typecheck, lint, jest) - Write clear commit messages:
fix: resolve prop merging issueorfeat: add keyboard navigation - Avoid committing generated files (
dist/,.next/,out/) - Use
.gitignoreto exclude build artifacts
References
See ./references/ for indexed documentation pointers.
Related Files
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です


