Mantine Custom Components Skill
Component template
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 = { radius: 'md' } satisfies Partial<MyComponentProps>;
const varsResolver = createVarsResolver<MyComponentFactory>((_theme, { radius }) => ({
root: { '--my-radius': 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 = '@mantine/core/MyComponent';
MyComponent.classes = classes;
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.
Factory type fields
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?: boolean; // true = sub-component; disables theme classNames/styles/vars
ctx?: MyContextType; // passed to styles/vars resolvers as third arg
signature?: (...) => JSX.Element; // only for genericFactory
}>
Theme integration
Users and the theme can override defaults via Component.extend():
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) } }),
}),
},
});
References
references/api.md — All imports: factory, useProps, useStyles, createVarsResolver, createSafeContext, StylesApiProps, CompoundStylesApiProps, BoxProps, ElementProps, theme helpers (getSize, getRadius, etc.)
references/patterns.md — Full examples: compound components with context, polymorphic component, generic component, theme integration
1---2name: mantine-custom-components3description: 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.4---5
6# Mantine Custom Components Skill
7
8## Component template
9
10```tsx
11import {
12 Box, BoxProps, createVarsResolver, ElementProps,
13 factory, Factory, getRadius, MantineRadius,
14 StylesApiProps, useProps, useStyles,
15} from '@mantine/core';
16import classes from './MyComponent.module.css';
17
18export type MyComponentStylesNames = 'root' | 'inner';
19export type MyComponentVariant = 'filled' | 'outline';
20export type MyComponentCssVariables = { root: '--my-radius' };
21
22export interface MyComponentProps
23 extends BoxProps, StylesApiProps<MyComponentFactory>, ElementProps<'div'> {
24 radius?: MantineRadius;
25}
26
27export type MyComponentFactory = Factory<{
28 props: MyComponentProps;
29 ref: HTMLDivElement;
30 stylesNames: MyComponentStylesNames;
31 vars: MyComponentCssVariables;
32 variant: MyComponentVariant;
33}>;
34
35const defaultProps = { radius: 'md' } satisfies Partial<MyComponentProps>;
36
37const varsResolver = createVarsResolver<MyComponentFactory>((_theme, { radius }) => ({
38 root: { '--my-radius': getRadius(radius) },
39}));
40
41export const MyComponent = factory<MyComponentFactory>((_props) => {
42 const props = useProps('MyComponent', defaultProps, _props);
43 const { classNames, className, style, styles, unstyled, vars, attributes, radius, ...others } = props;
44
45 const getStyles = useStyles<MyComponentFactory>({
46 name: 'MyComponent', classes, props,
47 className, style, classNames, styles, unstyled, vars, attributes, varsResolver,
48 });
49
50 return <Box {...getStyles('root')} {...others} />;
51});
52
53MyComponent.displayName = '@mantine/core/MyComponent';
54MyComponent.classes = classes;
55```
56
57## Factory variant — which to use
58
59| Scenario | Factory function | Type |
60|---|---|---|
61| Standard component | `factory()` | `Factory<{}>` |
62| Supports `component` prop (polymorphic) | `polymorphicFactory()` | `PolymorphicFactory<{}>` — add `defaultComponent` and `defaultRef` |
63| Props change based on a generic (e.g. `multiple`) | `genericFactory()` | `Factory<{ signature: ... }>` |
64
65Use `polymorphicFactory` sparingly — it adds TypeScript overhead and slows IDE autocomplete.
66
67## Factory type fields
68
69```ts
70Factory<{
71 props: MyComponentProps; // required
72 ref: HTMLDivElement; // element type for the forwarded ref
73 stylesNames: 'root' | 'inner'; // union of Styles API selectors
74 vars: { root: '--my-var' }; // CSS variable map per selector
75 variant: 'filled' | 'outline'; // accepted variant strings
76 staticComponents: { // sub-components (compound pattern)
77 Item: typeof MyComponentItem;
78 };
79 compound?: boolean; // true = sub-component; disables theme classNames/styles/vars
80 ctx?: MyContextType; // passed to styles/vars resolvers as third arg
81 signature?: (...) => JSX.Element; // only for genericFactory
82}>
83```
84
85## Theme integration
86
87Users and the theme can override defaults via `Component.extend()`:
88
89```ts
90const theme = createTheme({
91 components: {
92 MyComponent: MyComponent.extend({
93 defaultProps: { radius: 'xl' },
94 classNames: { root: 'my-root' },
95 styles: { root: { color: 'red' } },
96 vars: (_theme, props) => ({ root: { '--my-radius': getRadius(props.radius) } }),
97 }),
98 },
99});
100```
101
102## References
103
104- **[`references/api.md`](references/api.md)** — All imports: `factory`, `useProps`, `useStyles`, `createVarsResolver`, `createSafeContext`, `StylesApiProps`, `CompoundStylesApiProps`, `BoxProps`, `ElementProps`, theme helpers (`getSize`, `getRadius`, etc.)
105- **[`references/patterns.md`](references/patterns.md)** — Full examples: compound components with context, polymorphic component, generic component, theme integration