
Mantine はどうやって variant によってスタイルを変えているのか ?
2025年4月3日
Mantineがどのようにしてvariantごとにスタイルを定義して、
それがどうやって適用されているのかを調べてみました。
variantの例として、この記事ではAccordion
コンポーネントを例に解説します。
Mantineのバージョン: 7.17
variantとは ?
Mantineのコンポーネントにはvariantで見た目を変更できるものがあります。
variant="<variant名>"
のようにvariantを設定します。
<Accordion variant="<variant名>">
...
</Accordion>
どこでスタイルは定義されているのか ?
Accordion
のソース@mantine/core/src/components/Accordion/Accordion.tsx
を見てみると、Accordion.module.css
をimportしているので、ここでスタイル定義がされているのではと推測されます。
このcssファイルでは、以下のような形式でvariantごとのスタイルが定義されていました。
/*
.<selector>--<variant> { ... }
selectorとは、ドキュメントのStyles APIで書かれている root, item, control などのことです。
*/
.item--filled {
// スタイル定義
}
スタイル定義のされかたはわかりましたが、ではこのスタイルはどのようにして適用されるのでしょうか ?
例えば、<Accordion variant="filled">
としたとして、どのようにして、
.item--filled
が適用されるのでしょうか ?
Accordion.Item
のソース@mantine/core/src/components/Accordion/AccordionItem/AccordionItem.tsxを見てみると、
return (
<AccordionItemProvider value={{ value }}>
<Box
ref={ref}
mod={[{ active: ctx.isItemActive(value) }, mod]}
{...ctx.getStyles('item', { className, classNames, styles, style, variant: ctx.variant })}
{...others}
/>
</AccordionItemProvider>
);
から、ctx.getStyles(...)
がどうやらvariantに関連した処理をしていることがわかります。
このctxというのは、AccordionProvider
で、Accordion
から渡されるコンテキストです。
const getStyles = useStyles<AccordionFactory>({
name: 'Accordion',
classes,
props: props as AccordionProps,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver,
});
...
return (
<AccordionProvider
value={{
isItemActive,
onChange: handleItemChange,
getControlId: getSafeId(
`${uid}-control`,
'Accordion.Item component was rendered with invalid value or without value'
),
getRegionId: getSafeId(
`${uid}-panel`,
'Accordion.Item component was rendered with invalid value or without value'
),
transitionDuration,
disableChevronRotation,
chevronPosition,
order,
chevron,
loop,
getStyles,
variant,
unstyled,
}}
>
...
useStyles(...)
というのは, @mantine/core/src/core/styles-api/use-styles/use-styles.ts
で定義されており、
export function useStyles<Payload extends FactoryPayload>({
name,
classes,
props,
stylesCtx,
className,
style,
rootSelector = 'root' as NonNullable<Payload['stylesNames']>,
unstyled,
classNames,
styles,
vars,
varsResolver,
}: UseStylesInput<Payload>): GetStylesApi<Payload> {
const theme = useMantineTheme();
const classNamesPrefix = useMantineClassNamesPrefix();
const withStaticClasses = useMantineWithStaticClasses();
const headless = useMantineIsHeadless();
const themeName = (Array.isArray(name) ? name : [name]).filter((n) => n) as string[];
const { withStylesTransform, getTransformedStyles } = useStylesTransform({
props,
stylesCtx,
themeName,
});
return (selector, options) => ({
className: getClassName({
theme,
options,
themeName,
selector,
classNamesPrefix,
classNames,
classes,
unstyled,
className,
rootSelector,
props,
stylesCtx,
withStaticClasses,
headless,
transformedStyles: getTransformedStyles([options?.styles, styles]),
}),
style: getStyle({
theme,
themeName,
selector,
options,
props,
stylesCtx,
rootSelector,
styles,
style,
vars,
varsResolver,
headless,
withStylesTransform,
}),
});
}
getClassName(...)
がクラス名関連の処理をしていることが推測されます。
getClassName(...)
は @mantine/core/src/core/styles-api/use-styles/get-class-name/get-class-name.tsで
定義されており、
export function getClassName({
theme,
options,
themeName,
selector,
classNamesPrefix,
classNames,
classes,
unstyled,
className,
rootSelector,
props,
stylesCtx,
withStaticClasses,
headless,
transformedStyles,
}: GetClassNameOptions) {
return cx(
getGlobalClassNames({ theme, options, unstyled: unstyled || headless }),
getThemeClassNames({ theme, themeName, selector, props, stylesCtx }),
getVariantClassName({ options, classes, selector, unstyled }),
getResolvedClassNames({ selector, stylesCtx, theme, classNames, props }),
getResolvedClassNames({ selector, stylesCtx, theme, classNames: transformedStyles, props }),
getOptionsClassNames({ selector, stylesCtx, options, props, theme }),
getRootClassName({ rootSelector, selector, className }),
getSelectorClassName({ selector, classes, unstyled: unstyled || headless }),
withStaticClasses &&
!headless &&
getStaticClassNames({
themeName,
classNamesPrefix,
selector,
withStaticClass: options?.withStaticClass,
}),
options?.className
);
}
cx == clsx
を使って、複数のクラス名を連結しており、
getVariantClassName(...)
がvariant関連のクラス名を生成していることが推測できます。
getVariantClassName(...)
は、@mantine/core/src/core/styles-api/use-styles/get-class-name/get-variant-class-name/get-variant-class-name.ts で
定義されており、
export function getVariantClassName({
options,
classes,
selector,
unstyled,
}: GetVariantClassNameInput) {
return options?.variant && !unstyled ? classes[`${selector}--${options.variant}`] : undefined;
}
から、variantが指定されているとき、<selector>--<variant>
がクラス名として追加されるということがわかりました。
このclassesとは何かというと、css modules ファイルをインポートしたもので、
useStyles(...)
に渡されたclassesがバケツリレーで渡されたものです。
import classes from './Accordion.module.css';
...
const getStyles = useStyles<AccordionFactory>({
name: 'Accordion',
classes,
props: props as AccordionProps,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver,
});
というわけで、
どのようにしてvariantのクラス名が適用されるのかをまとめると、
useStyles(...)でgetStyles(...)を作成し、
getStyles(...) -> getClassName(...) -> getVariantClassName(...)
という流れで、variantのクラス名が選択されていたのでした。
