Top
/
Articles
/
Mantine はどうやって variant によってスタイルを変えているのか ?
カテゴリー「Mantine」の画像

Mantine はどうやって variant によってスタイルを変えているのか ?

Mantine
ソース読み

2025年4月3日

Mantineがどのようにしてvariantごとにスタイルを定義して、
それがどうやって適用されているのかを調べてみました。
variantの例として、この記事ではAccordion コンポーネントを例に解説します。

Mantineのバージョン: 7.17

variantとは ?

Mantineのコンポーネントにはvariantで見た目を変更できるものがあります。
variant="<variant名>" のようにvariantを設定します。

<Accordion variant="<variant名>">
...
</Accordion>

variantの例

どこでスタイルは定義されているのか ?

Accordionのソース@mantine/core/src/components/Accordion/Accordion.tsx
を見てみると、Accordion.module.css をimportしているので、ここでスタイル定義がされているのではと推測されます。

このcssファイルでは、以下のような形式でvariantごとのスタイルが定義されていました。

@mantine/core/src/components/Accordion/Accordion.module.css
/*
.<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を見てみると、

@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 から渡されるコンテキストです。

@mantine/core/src/components/Accordion/Accordion.tsx
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
で定義されており、

@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
定義されており、

@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
定義されており、

@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がバケツリレーで渡されたものです。

@mantine/core/src/components/Accordion/Accordion.tsx
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のクラス名が選択されていたのでした。

横長宣伝