About mantine-custom-components
A skill for developers building custom components with Mantine 9.x. It covers component structure, styling, variants, theme integration, and choosing the appropriate factory pattern.
Key features
- 01Mantine 9.x component template
- 02Styles API props and classes
- 03Variants, sizes, and CSS variables
- 04Theme color and size helpers
- 05Factory patterns and component types
- 06Theme extension and prop presets
Use cases
- 01Creating a custom Mantine component
- 02Adding variants and size styles
- 03Connecting component defaults to a theme
- 04Choosing a factory for polymorphic or generic props
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ mantinedev/ skills --skill mantine-custom-componentsAfter installing, type/mantine-custom-componentsin Claude Code to confirm it loaded.
Bundled filesreferences/api.mdreferences/patterns.md
Details
From the project READMEMantine Custom Components Skill
Written for Mantine 9.x.
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 = {} 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;
/* 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 classmantine-MyComponent-selector. classNames,styles,varsandattributesprops, and the same keys inMyComponent.extend()in the theme.unstyledremoves the CSS module classes. Static classes and the CSS variables on the root stay.MyComponent.extend()andMyComponent.withProps()static functions.withPropspresets 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:
const { variant, size, ...others } = props;
<Box variant={variant} size={size} {...getStyles('root', { variant })} {...others} />
.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))).
root: { '--card-padding': getSize(size, 'card-padding'), '--card-fz': getFontSize(size) },
.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.
.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
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():
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— 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 forwardclassNames/stylesto it, "Components that share theme configuration" for a family of components with a common base, "Converting an existing component" when migrating aforwardRefcomponent with inline styles, "Polymorphic component" for acomponentprop, "Generic component" for props that depend on a type parameter, "Theme integration" forextendandwithPropsreferences/api.md— read it for any component beyond the template above: it is the only place that documentsgetStylesoptions (focusable), themodprop, theme helper outputs,MantineThemeProviderand what theme functions receive. Every function and type:factoryvariants,useProps,useStylesandgetStylesoptions,createVarsResolver,createSafeContext,StylesApiProps,CompoundStylesApiProps,BoxProps(includingmod),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, usesearch_docs,get_item_docandget_api - Otherwise fetch
https://mantine.dev/llms.txtand open the Styles API, variants and sizes, and custom components pages