Back to list
gfazioli

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.

2🍴 0📅 Jan 23, 2026

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 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/ 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 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.

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:

  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.

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
  • Never use camelCase or PascalCase in CSS (e.g., ledPulse is invalid, use led-pulse)

Property Order:

  • Avoid using CSS shorthand properties (like background) after their longhand equivalents (like background-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 lint to catch CSS linting errors
  • stylelint enforces:
    • declaration-block-no-shorthand-property-overrides: Prevents shorthand/longhand conflicts
    • keyframes-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

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 .env files in .gitignore

Dependency Hygiene

  • Keep @mantine/core and @mantine/hooks versions in sync (see ./package/package.json)
  • Use peerDependencies for React and Mantine packages
  • Run npm run syncpack to verify version consistency

Least Privilege

  • Minimize scope of React Context providers
  • Avoid global state; prefer component-level state
  • Use createSafeContext to enforce provider boundaries

Edge Cases and Troubleshooting

Module Resolution

  • Ensure all imports use relative paths for local modules
  • Check package.json exports field for correct entry points
  • Verify tsconfig.json paths if 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 getStyles helper for className merging

Polymorphic Components

  • Default component is div; override with component prop
  • Ref type must match defaultRef in factory definition
  • Props are merged: BoxProps & ComponentProps & { component?: any }

Testing Library Queries

  • Prefer getByRole over getByTestId
  • Use screen for global queries or container for scoped
  • Mock window.matchMedia if needed (see ./jsdom.mocks.cjs)

Storybook Integration

  • Stories go in Component.story.tsx alongside component
  • Use CSF3 format with Meta and StoryObj types
  • 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 test before committing (includes prettier, typecheck, lint, jest)
  • Write clear commit messages: fix: resolve prop merging issue or feat: add keyboard navigation
  • Avoid committing generated files (dist/, .next/, out/)
  • Use .gitignore to exclude build artifacts

References

See ./references/ for indexed documentation pointers.

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon