Skip to main content
skill CX Codex

mantine-custom-components — Claude Code skill: how to install

Teaches AI agents to build custom components for Mantine 9.x and React 19. It sets up component factories, Styles API support, and CSS variable resolvers.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add mantinedev/skills --skill mantine-custom-components -a claude-code
Open on GitHub

What mantine-custom-components does

This skill guides the agent through constructing custom UI components that follow Mantine 9.x architecture. It covers component creation using factory(), polymorphicFactory(), and genericFactory(). Every generated component includes native Styles API properties like classNames, styles, vars, and unstyled from @mantine/core, along with static class names on every styled selector.

CSS variables link through createVarsResolver, while size tokens run through the getSize utility. Colors resolve via theme.variantColorResolver, allowing themes to reconfigure component variants without code edits. Be deliberate with polymorphic components. The author notes that polymorphicFactory adds noticeable TypeScript overhead and slows editor autocomplete, favoring renderRoot instead.

React 19 handles ref as a standard prop passed through to the root element. For compound component structures, the skill details sub-component registration via staticComponents and isolates theme styles using the compound: true flag.

What it helps with

  • create a custom Mantine 9 component using factory or genericFactory
  • implement Styles API support with classNames, styles, and unstyled
  • map component theme tokens and CSS variables via createVarsResolver
  • build compound components with shared context and static sub-components

Example prompts for the agent

  • Build a custom Card component using factory() from @mantine/core with Styles API support
  • Add size variants to my Mantine component using getSize and CSS custom properties
  • Create a compound component with an Item sub-component and compound: true

How to install mantine-custom-components

1Skills CLI (Vercel)

npx skills add mantinedev/skills --skill mantine-custom-components -a claude-code

2Manually: put the files in place

~/.claude/skills/mantine-custom-components/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/mantine-custom-components/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about mantine-custom-components

How to install mantine-custom-components in Claude Code?

Run npx skills add mantinedev/skills --skill mantine-custom-components -a claude-code in your terminal to install the skill for Claude Code.

What does mantine-custom-components do?

It provides prompt guidance and code patterns for building components in Mantine 9.x. The skill instructs agents on using factory functions, managing the Styles API, and wiring CSS variables via createVarsResolver.

Why should polymorphicFactory be used sparingly?

Polymorphic factories add heavy TypeScript type-checking overhead that can slow down IDE autocomplete. For changing the root element without the type tax, components support the renderRoot prop instead.

Source text

SKILL.md · MIT
---
name: mantine-custom-components
description: >
  Build custom components that integrate with Mantine's theming, Styles API, and core features.
  Use this skill when: (1) creating a new component using factory(), polymorphicFactory(), or
  genericFactory(), (2) adding Styles API support (classNames, styles, vars, unstyled), (3)
  implementing CSS variables via createVarsResolver, (4) building compound components with
  sub-components and shared context, (5) registering a component with MantineProvider via
  Component.extend(), or (6) any task involving Factory, useProps, useStyles, BoxProps,
  StylesApiProps, or ElementProps in @mantine/core.
---

# Mantine Custom Components Skill

Written for Mantine 9.x.

## Component template

```tsx
import {
  Box, BoxProps, createVarsResolver, ElementProps,
  factory, Factory, getRadius, MantineRadius,
  StylesApiProps, useProps, useStyles,
} from '@mantine/core';
import classes from './MyComponent.module.css';

export type MyComponentStylesNames = 'root' | 'inner';
export type MyComponentVariant = 'filled' | 'outline';
export type MyComponentCssVariables = { root: '--my-radius' };

export interface MyComponentProps
  extends BoxProps, StylesApiProps<MyComponentFactory>, ElementProps<'div'> {
  radius?: MantineRadius;
}

export type MyComponentFactory = Factory<{
  props: MyComponentProps;
  ref: HTMLDivElement;
  stylesNames: MyComponentStylesNames;
  vars: MyComponentCssVariables;
  variant: MyComponentVariant;
}>;

const defaultProps = {} satisfies Partial<MyComponentProps>;

const varsResolver = createVarsResolver<MyComponentFactory>((_theme, { radius }) => ({
  // undefined leaves the variable unset, so the CSS fallback (theme default radius) applies
  root: { '--my-radius': radius === undefined ? undefined : getRadius(radius) },
}));

export const MyComponent = factory<MyComponentFactory>((_props) => {
  const props = useProps('MyComponent', defaultProps, _props);
  const { classNames, className, style, styles, unstyled, vars, attributes, radius, ...others } = props;

  const getStyles = useStyles<MyComponentFactory>({
    name: 'MyComponent', classes, props,
    className, style, classNames, styles, unstyled, vars, attributes, varsResolver,
  });

  return <Box {...getStyles('root')} {...others} />;
});

MyComponent.displayName = 'MyComponent';
MyComponent.classes = classes;
MyComponent.varsResolver = varsResolver;
```

```css
/* MyComponent.module.css */
.root {
  border-radius: var(--my-radius, var(--mantine-radius-default));
}
```

`ref` is a regular prop in React 19: it arrives in `props` and reaches the root element through `...others`.

What you get without extra code:

- Every element rendered with `getStyles('selector')` gets the static class `mantine-MyComponent-selector`.
- `classNames`, `styles`, `vars` and `attributes` props, and the same keys in `MyComponent.extend()` in the theme.
- `unstyled` removes the CSS module classes. Static classes and the CSS variables on the root stay.
- `MyComponent.extend()` and `MyComponent.withProps()` static functions. `withProps` presets props at
  runtime but does not change types: a required prop stays required for TypeScript, so make props you
  intend to preset optional.

## Variants and sizes

Pass `variant` and `size` to `Box`: it sets `data-variant` and `data-size` attributes to style in CSS.
Passing `variant` to `getStyles` additionally applies the `root--{variant}` class when the CSS module defines one:

```tsx
const { variant, size, ...others } = props;

<Box variant={variant} size={size} {...getStyles('root', { variant })} {...others} />
```

```css
.root {
  &[data-variant='outline'] { border: 1px solid var(--my-color); }
  &[data-size='lg'] { height: 50px; }
}
```

Sizes driven by one `size` prop: define the token values in CSS and select one with `getSize`. A
number or any CSS value passed as `size` is used as is (`getSize(60, 'x')` returns `calc(3.75rem * var(--mantine-scale))`).

```tsx
root: { '--card-padding': getSize(size, 'card-padding'), '--card-fz': getFontSize(size) },
```

```css
.root {
  --card-padding-sm: 12px;
  --card-padding-md: 16px;
  --card-padding-lg: 24px;

  padding: var(--card-padding, var(--card-padding-md));
}
```

For colors that follow the theme, resolve them in the vars resolver with
`theme.variantColorResolver({ color: color || theme.primaryColor, theme, variant: variant || 'filled', autoContrast })`
— it returns `background`, `hover` and `color` as colors and `border` as a full `border` shorthand value
(`1px solid transparent`). It knows the variants `filled`, `light`, `outline`, `subtle`, `transparent`, `white` and
`default`; `autoContrast: undefined` falls back to `theme.autoContrast`. For a single color use
`getThemeColor(color, theme)`: it accepts `'blue'`, `'teal.7'` and CSS colors.

```css
.root {
  background-color: var(--my-bg);
  color: var(--my-color);
  border: var(--my-bd); /* the whole shorthand, not border-color */
}
```

Because the resolver comes from the theme, an app can add its own variant (`variant="danger"`) or
recolor an existing one without touching the component. Do not redeclare `variant` in your props
interface: `StylesApiProps` already types it as your variants plus any string.

## Factory variant — which to use

| Scenario | Factory function | Type |
|---|---|---|
| Standard component | `factory()` | `Factory<{}>` |
| Supports `component` prop (polymorphic) | `polymorphicFactory()` | `PolymorphicFactory<{}>` — add `defaultComponent` and `defaultRef` |
| Props change based on a generic (e.g. `multiple`) | `genericFactory()` | `Factory<{ signature: ... }>` |

Use `polymorphicFactory` sparingly — it adds TypeScript overhead and slows IDE autocomplete.
Every factory component also accepts `renderRoot={(props) => <a {...props} />}` as an alternative to `component`.

## Factory type fields

```ts
Factory<{
  props: MyComponentProps;       // required
  ref: HTMLDivElement;           // element type for the forwarded ref
  stylesNames: 'root' | 'inner'; // union of Styles API selectors
  vars: { root: '--my-var' };    // CSS variable map per selector
  variant: 'filled' | 'outline'; // accepted variant strings
  staticComponents: {            // sub-components (compound pattern)
    Item: typeof MyComponentItem;
  };
  compound: true;                // only for sub-components; disables theme classNames/styles/vars
  ctx: { stepsCount: number };   // only if needed; passed to classNames/styles/vars functions as third arg
  signature?: (...) => JSX.Element; // only for genericFactory
}>
```

## Theme integration

Users and the theme can override defaults via `Component.extend()`:

```ts
const theme = createTheme({
  components: {
    MyComponent: MyComponent.extend({
      defaultProps: { radius: 'xl' },
      classNames: { root: 'my-root' },
      styles: { root: { color: 'red' } },
      vars: (_theme, props) => ({ root: { '--my-radius': getRadius(props.radius) } }),
    }),
  },
});
```

In `theme.components`, sub-components are registered without the dot: `MyCardSection: MyCard.Section.extend({ defaultProps })`.

## References

Read the part that matches the task before writing code:

- **[`references/patterns.md`](references/patterns.md)** — complete examples. Read "Compound component with context" for components with sub-components (`Card.Section`), "Wrapping a Mantine component" when the component renders an existing Mantine input or component inside and must forward `classNames` / `styles` to it, "Components that share theme configuration" for a family of components with a common base, "Converting an existing component" when migrating a `forwardRef` component with inline styles, "Polymorphic component" for a `component` prop, "Generic component" for props that depend on a type parameter, "Theme integration" for `extend` and `withProps`
- **[`references/api.md`](references/api.md)** — read it for any component beyond the template above: it is the only place that documents `getStyles` options (`focusable`), the `mod` prop, theme helper outputs, `MantineThemeProvider` and what theme functions receive. Every function and type: `factory` variants, `useProps`, `useStyles` and `getStyles` options, `createVarsResolver`, `createSafeContext`, `StylesApiProps`, `CompoundStylesApiProps`, `BoxProps` (including `mod`), `ElementProps`, theme helpers (`getSize`, `getRadius`, `getThemeColor`...)

## Looking things up

If the references do not cover what you need, do not guess:

- If the Mantine MCP server (`@mantine/mcp-server`) is connected, use `search_docs`, `get_item_doc` and `get_api`
- Otherwise fetch `https://mantine.dev/llms.txt` and open the Styles API, variants and sizes, and custom components pages

© mantinedev · MIT · mantinedev/skills

Similar skills

Everything for Frontend and UI →

From the same set3

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor