🎃

Yamada UIのカスタマイズ

2024/04/21に公開

はじめに

Yamada UIのメンテナーをしていますが、まだまだ知らない機能がたくさんあります。
その機能yamadaさんしか知らないのでは?みたいな機能もたくさんあります。

公式ガイドのほうに情報は網羅されていますが複雑なので、初めて使っていただく方に、最低限これだけ押さえておけばというのをお伝えできればと思います。
内容は結構端折っているので、もっと詳しく知りたい方はドキュメントを読んでください。

https://yamada-ui.com/ja/getting-started

推奨する使い方

各コンポーネントはスタイルのpropsを設定することでスタイリングすることができます。

<Container bg="green.100" p="md" fontSize="md" borderWidth="1px">
  This is Container
</Container>

ただ、この方法でのスタイリングは最小限にとどめるべきです。
Yamada UIでは、コンポーネントのスタイルをまとめて管理することができます。
全てのContainerに以下のスタイルが適用できるようになります。

./theme/components/container.ts
import { ComponentStyle } from "@yamada-ui/react"

export const Container: ComponentStyle = {
 // すべての場合に設定されるスタイル
  baseStyle: {
    bg: "green.100",
  },
  //size="sm"とかsize="md"のようにしたときに設定されるスタイル
  sizes: {
    sm: { p: "sm", fontSize: "sm" },
    md: { p: "md", fontSize: "md" },
    lg: { p: "lg", fontSize: "lg" },
  },
  // variant="with-border-dotted"のようにしたときに設定されるスタイル
  variants: {
    "wtih-border-solid": {
      borderWidth: "1px",
    },
    "wtih-border-dotted": {
      borderWidth: "1px",
      borderStyle: "dotted",
    },
  },
  // sizeやvariantを何も指定しなかった場合、どれを適用させるか
  defaultProps: {
    size: "md",
    variant: "wtih-border-solid",
  },
}
<Container size="sm" variant="with-border-dotted">
  This is Container
</Container>

こうすることでスタイルを一元管理でき、例えば、サイト全体のスタイルを修正するとなった場合にも、theme/以下のファイルを編集するだけで達成できます。
例外として個別にスタイリングする必要がある場合だけ、コンポーネントに直接スタイルのpropsを渡しましょう。

https://yamada-ui.com/ja/styled-system/theming/customize-theme#コンポーネントのスタイルを変更する

Yamada UIには、ほかにも一元的にスタイルを管理するための機能が備わっています。それを紹介していきます。

カスタマイズ

themeフォルダを作成し、管理します。

./theme
├ components # 各コンポーネントのスタイルを定義するフォルダ
├ tokens # 各トークンを定義するフォルダ
├ styles # グローバルスタイルやリセットスタイルを定義するフォルダ
├ semantics.ts # セマンティックトークンを設定するファイル
├ config.ts # コンフィグを設定するファイル
└ index.ts # テーマをデフォルトでエクスポートするファイル

index.tsでまとめてexportします。
いらないものは適宜削除してください。
extendThemeを使うと、デフォルトのテーマに上書きすることができます。

index.ts
import { extendTheme, extendConfig, UsageTheme } from "@yamada-ui/react"
import styles from "./styles"
import components from "./components"
import tokens from "./tokens"
import { semantics } from "./semantics"
import { customConfig } from "./config"

const customTheme: UsageTheme = {
  styles,
  components,
  semantics,
  ...tokens,
}

export const theme = extendTheme(customTheme)()
export const config = extendConfig(customConfig)

作成したthemeUIProviderに渡せば反映されます。

import { UIProvider } from "@yamada-ui/react"
import { theme, config } from "theme"

...

<UIProvider theme={theme} config={config}>
  <App />
</UIProvider>

それぞれ詳しく説明していきます。

components

各コンポーネントごとのスタイルをまとめて管理できます。
これにより、スタイルを柔軟に維持していくことが可能です。
https://yamada-ui.com/ja/styled-system/theming/customize-theme#コンポーネントのスタイルを変更する
https://yamada-ui.com/ja/styled-system/theming/component-styles
カスタマイズしたいコンポーネントごとにファイルを作成します。

./theme/comonents
├ container.ts
├ button.ts
└ index.ts

index.tsでまとめてexportします。

index.ts
import { Container } from "@yamada-ui/react"
import { Button } from "@yamada-ui/react"

export default { Container, Button }

基本的な設定

冒頭で載せちゃいましたが、以下のようにすることで、containerはすべてこのスタイルが適用されます。

container.ts
import { ComponentStyle } from "@yamada-ui/react"

export const Container: ComponentStyle = {
 // すべての場合に設定されるスタイル
  baseStyle: {
    bg: "green.100",
  },
  //size="sm"とかsize="md"のようにしたときに設定されるスタイル
  sizes: {
    sm: { p: "sm", fontSize: "sm" },
    md: { p: "md", fontSize: "md" },
    lg: { p: "lg", fontSize: "lg" },
  },
  // variant="with-border-dotted"のようにしたときに設定されるスタイル
  variants: {
    "wtih-border-solid": {
      borderWidth: "1px",
    },
    "wtih-border-dotted": {
      borderWidth: "1px",
      borderStyle: "dotted",
    },
  },
  // sizeやvariantを何も指定しなかった場合、どれを適用させるか
  defaultProps: {
    size: "md",
    variant: "wtih-border-solid",
  },
}

デフォルトの設定がどうなっているかは、各コンポーネントのページのテーマタブで見れます。
https://yamada-ui.com/ja/components/layouts/container/theming

複数コンポーネントの設定がある場合

複数コンポーネントがあるような場合は、型がComponentMultiStyleになり、さらに一段階ネストしてそれぞれのコンポーネントのスタイルを記述します。

補足

Accordionには、AccordionItem等の複数コンポーネントがあります。
このように複数コンポーネントがある場合もありますが、AreaChartのように、内部で複数コンポーネントを使用している場合もComponentMultiStyleになります。
単一コンポーネントか複数コンポーネントかはドキュメントのテーマタブを参照してください。
https://yamada-ui.com/ja/components/disclosure/accordion/theming

Accordionのテーマのデフォルト設定は以下のようになっています。container,item,button,panel,iconにそれぞれのコンポーネントのスタイルを設定します。

accordion.ts
export const Accordion: ComponentMultiStyle = {
  baseStyle: {
    container: {},
    item: {},
    button: {
      transitionProperty: "common",
      transitionDuration: "normal",
      _focusVisible: {
        boxShadow: "outline",
      },
      _disabled: {
        opacity: 0.4,
        cursor: "not-allowed",
      },
      py: "3",
      px: "4",
    },
    panel: {
      px: "4",
      pb: "3",
    },
    icon: {
      ml: "auto",
      fontSize: "1.25em",
      color: ["blackAlpha.600", "whiteAlpha.700"],
    },
  },

  variants: {
    basic: {
      item: {
        borderTopWidth: "1px",
        borderColor: "inherit",
        _last: {
          borderBottomWidth: "1px",
        },
      },
      button: {
        _hover: {
          bg: ["blackAlpha.50", "whiteAlpha.50"],
          _disabled: {
            bg: "none",
          },
        },
      },
    },
    card: {
      item: {
        borderWidth: "1px",
        rounded: "md",
        bg: ["blackAlpha.50", "whiteAlpha.50"],
        _expanded: {
          bg: ["white", "black"],
        },
        _notFirst: {
          mt: "md",
        },
      },
      button: {
        _hover: {
          bg: ["blackAlpha.100", "whiteAlpha.100"],
          _expanded: {
            bg: "none",
          },
          _disabled: {
            bg: "none",
          },
        },
      },
    },
    unstyled: {},
  },

  defaultProps: {
    variant: "basic",
  },
}

スタイルを動的に変更する

以下のように関数を使用して動的にスタイルを設定することができます。
カラースキーム等に応じてスタイルを変更する場合に利用します。テーマやカラーモード以外にも、そのコンポーネントに渡されたPropsであれば、受け取ることが可能です。

補足

この後も出てきますが、以下のような使い分けです。

  • テーマ(theme): Tipsの複数のテーマを切り替えるのように、複数のテーマがある場合に、現在のテーマを取得できます。
  • カラーモード(colorMode): ダークモード/ライトモードを取得できます。
  • カラースキーム(colorScheme): <Button colorScheme="primary" />のように渡された値が取得できます。トークンを追加するでカラースキームをつかした場合は、それも渡すことができます。
container.ts
import {
  ComponentStyle,
  isGray,
  isAccessible,
 } from "@yamada-ui/react"

export const Container: ComponentStyle = {
 ...

  variants: {
    "wtih-border-solid": ({theme: t, colorMode: m, colorScheme: c}) => ({
      bg: isGray(c)
          ? [`${c}.50`, `${c}.700`]
          : [isAccessible(c) ? `${c}.400` : `${c}.500`, `${c}.600`],
      borderWidth: "1px",
    }),
    "wtih-border-dotted": {
      borderWidth: "1px",
      borderStyle: "dotted",
    },
  },

  ...
}

Buttonはデフォルトで結構カスタマイズされているので参考にしてみてください。

Buttonのデフォルトのテーマ設定

tokens

新しくトークンを追加したり、既存のトークンを変更することができます。
https://yamada-ui.com/ja/styled-system/theming/customize-theme#トークンを変更する
トークンの種別ごとにファイルを作成します。

./theme/tokens
├ colors.ts
├ fontSize.ts
└ index.ts

index.tsでまとめてexportします。

index.ts
import { colors } from "./colors"
import { fontSize } from "./fontSize"

export default { colors, fontSize }

トークンとは

fontsizeなどを指定するときに使うsm, mdから、z-indexのyamcha, kurillinなどがデフォルトで設定されています。
colorの指定に利用できるred.500などもトークンになります。
これらはコンポーネントのスタイルを設定するときに利用できます。
僕はドラゴンボール見てないので、クリリンがどれくらいの強さなのかわかりません。

// <Button fontSize="0.875rem" zIndex={9} color="#ea4334" />
<Button fontSize="sm" zIndex="kurillin" color="red.500" />

デフォルトで、どういったトークンがあるかは以下のドキュメントに記載されています。
https://yamada-ui.com/ja/styled-system/theming/default-theme
https://github.com/yamada-ui/yamada-ui/tree/main/packages/theme/src/tokens

トークンを追加する

ドキュメントそのままですが、以下の例ではbannerというトークンを新たに追加し、blackというデフォルトで定義されているトークンを上書きしています。

./theme/tokens/colors.ts
import { ThemeTokens } from "@yamada-ui/react"

export const colors: ThemeTokens = {
  banner: "#9d38a0",
  black: ["#1F2123", "#101112"],
}

Yamada Colors

Yamada UIにはデフォルトでも豊富なカラースキームが提供されていますが、これ以外の色をカラースキームとして追加したい場合があるかもしれません。
https://yamada-ui.com/ja/styled-system/theming/default-theme

こんな時に使えるのが、Yamada Colorsです!
(ドキュメントの左メニューの下の方にある「カラージェネレーター」)
https://yamada-colors.app/
カラースキームとして追加したい色を選択して、My PalettersからJSONでエクスポートします。それをそのままコピペでトークンとして追加できます。

スクショ


./theme/tokens/colors.ts
import { ThemeTokens } from "@yamada-ui/react"

export const colors: ThemeTokens = {
  banner: "#9d38a0",
  black: ["#1F2123", "#101112"],
  himmel: {
    "50": "#f7f9fc",
    "100": "#f0f4fa",
    "200": "#e0e9f5",
    "300": "#d1def0",
    "400": "#c2d3eb",
    "500": "#b3c8e6",
    "600": "#81a4d5",
    "700": "#4f80c4",
    "800": "#34609d",
    "900": "#24416b",
    "950": "#1b3252",
  }
}

styles

グローバルに適用するスタイルや、繰り返し使用するテキストのスタイル、レイヤーのスタイルをまとめて管理できます。
https://yamada-ui.com/ja/styled-system/theming/customize-theme#スタイルを変更する

カスタマイズしたいstyleごとにファイルを作成します。

./theme/styles
├ global-style.ts
├ reset-style.ts
├ layer-styles.ts
├ text-styles.ts
└ index.ts

index.tsでまとめてexportします。

index.ts
import { globalStyle } from "./global-style"
import { resetStyle } from "./reset-style"
import { layerStyles } from "./layer-styles"
import { textStyles } from "./text-styles"

export default { globalStyle, resetStyle, layerStyles, textStyles }

globalStyle

bodyのスタイルの変更など、グローバルのスタイルの変更ができます。
https://yamada-ui.com/ja/styled-system/global-styles
以下のようにすると、背景色が変更できます。

global-style.ts
import { UIStyle } from "@yamada-ui/react"

export const globalStyle: UIStyle = {
  body: {
    bg: ["sky.50", "sky.950"],
  },
}

関数にすることも可能です。関数の場合は、themecolorModeなどを取得できます。

global-style.ts
import { UIStyle } from "@yamada-ui/react"

export const globalStyle: UIStyle = ({ theme, colorMode }) => ({
  body: {
    bg: "red.600",
    color: "white",
  },
})

resetStyle

各ブラウザで設定されている、リセットスタイルの変更ができます。
globalStyleと同様に、スタイルオブジェクトまたはスタイルオブジェクトを返す関数を定義できます。
https://yamada-ui.com/ja/styled-system/global-styles

layerStyles

繰り返し使用するレイヤーのプロパティを定義しておくことが可能です。
https://yamada-ui.com/ja/styled-system/text-and-layer-styles
以下のようにテーマにトークンとスタイルを定義する必要があります。

layer-styles.ts
import { LayerStyles } from "@yamada-ui/react"

export const layerStyles: LayerStyles = {
  masterRoshi: {
    position: "relative",
    m: "md",
    boxSize: "4xs",
    border: "6px solid #000",
    rounded: "full",
    bg: "#FFF",
    fontFamily: "serif",
    color: "#000",
    fontSize: "8xl",
    fontWeight: "bold",
    _after: {
      content: '""',
      position: "absolute",
      top: "50%",
      left: "50%",
      transform: "translate(-50%, -50%)",
      zIndex: -1,
      rounded: "md",
      bg: "#FF7F0B",
      boxSize: "3xs",
    },
  },
}

追加したlayerStyleは以下のように利用できます。

<Center layerStyle="masterRoshi"></Center>

textStyles

繰り返し使用するテキストのプロパティを定義しておくことが可能です。
https://yamada-ui.com/ja/styled-system/text-and-layer-styles
使い方はlayerStyleと同様です。

text-styles.ts
export const textStyles: TextStyles = {
  gradient: {
    fontSize: "5xl",
    bgGradient: "linear(to-r, orange.400, red.500)",
    bgClip: "text",
  },
}
<Heading textStyle="gradient" isTruncated>
  クリリンのことか……クリリンのことかーーーっ!!!!!
</Heading>

その他のスタイル

layerStyletextStyle以外にも独自のスタイルを定義できます。
https://yamada-ui.com/ja/styled-system/text-and-layer-styles#その他のスタイル

gangnam-style.ts
import { CSSUIObject } from "@yamada-ui/react"

export const gangnamStyle: Record<string, CSSUIObject> = {
  title: {
    fontSize: "2xl",
    bg: "black",
    color: "blue.700",
  },
  text: {
    bg: "yellow.700",
  },
}

index.tsで一緒にexportします。

index.ts
import { globalStyle } from "./global-style"
import { resetStyle } from "./reset-style"
import { layerStyles } from "./layer-styles"
import { textStyles } from "./text-styles"
+import { gangnamStyle } from "./gangnam-styles"

-export default { globalStyle, resetStyle, layerStyles, textStyles }
+export default { globalStyle, resetStyle, layerStyles, textStyles, gangnamStyle }

独自に定義したスタイルを使用する場合はapplyに渡します。

<Text as="h1" apply="gangnamStyle.title">
  フリーレン様。カンナムスタイルは最近の曲ではありません。
</Text>

semantics

primarysecondary等の色を変更できます。
https://yamada-ui.com/ja/styled-system/theming/customize-theme#セマンティックトークンを変更する

以下のようにすることで変更が可能です。
colorSchemesに設定できるのは、red.50~red.950のように、セットになっているものです。もちろんYamada Colorsで追加したhimmelも利用できます。

デフォルトの設定
./theme/semantics.ts
import { ThemeSemantics } from "@yamada-ui/react"

export const semantics: ThemeSemantics = {
  colors: {
    primary: "himmel.500",
  },
  colorSchemes: {
    primary: "himmel",
  },
}

config

デフォルトのカラーモードやテーマ、CSSカスタムプロパティのプレフィックスを変更することができます。
https://yamada-ui.com/ja/styled-system/configure/customize-config

デフォルトのconfig

標準でシステム設定にあわせたい場合は、以下のようにします。

./theme/config.ts
export const config: ThemeConfig = {
  initialColorMode: "system",
}

Tips

デフォルトのテーマ設定は以下のフォルダに入ってます。
https://github.com/yamada-ui/yamada-ui/tree/main/packages/theme/src

ライトモード/ダークモードの切り替え

Yamada UIは標準でダークモードとライトモードの切り替えをサポートしています。
https://yamada-ui.com/ja/styled-system/color-mode
useColorMode()でカラーモードの切り替えができます。

const { colorMode, changeColorMode, toggleColorMode } = useColorMode();

return (
  <Wrap gap="md">
    <Button onClick={() => changeColorMode("light")}>ライトモード</Button>
    <Button onClick={() => changeColorMode("dark")}>ダークモード</Button>
    <Button onClick={() => changeColorMode("system")}>システム</Button>
    <Button onClick={toggleColorMode}>
      {colorMode === "light" ? "ダーク" : "ライト"}モードに切り替える
    </Button>
  </Wrap>
);

複数のテーマを切り替える

Yamada UIは複数のテーマを用意しておき、それを切り替えできる機能を提供しています。
複数のsemanticsを切り替えできるイメージです。
https://yamada-ui.com/ja/styled-system/theming/switch-themes
semantics.tsを以下のように書き換えて、複数のsemanticsを定義しておきます。

./theme/semantics.ts
import { ThemeSchemes } from "@yamada-ui/react"

export const themeSchemes: ThemeSchemes = {
  frieren: {
    semantics: {
      colors: {
        primary: "frieren.500",
        secondary: "himmel.500",
      },
      colorSchemes: {
        primary: "frieren",
        secondary: "himmel",
      },
    },
  },
  fern: {
    semantics: {
      colors: {
        primary: "fern.500",
        secondary: "stark.500",
      },
      colorSchemes: {
        primary: "fern",
        secondary: "stark",
      },
    },
  },
}

index.tsも合わせて変更します。

./theme/index.ts
-import { semantics } from "./semantics"
+import { themeSchemes } from "./semantics"

export const customTheme: UsageTheme = {
  styles,
  components,
-  semantics,
+  themeSchemes,
  ...tokens,
}

テーマの切り替えはuseThemeを使って以下のようにできます。
changeThemeSchemeに切り替え先のテーマ名を渡しましょう。

const { theme, changeThemeScheme } = useTheme()

return (
  <Button onClick={() => {changeThemeScheme("fern")}>
    fern
  </Button>
)

ThemeSchemeScript

複数のテーマを切り替えるでテーマを正常に切り替えるために、headもしくはbody内にThemeSchemeScriptを追加する必要があります。
ThemeSchemeScriptを追加しない場合、テーマ切り替えのちらつきが発生します。
https://yamada-ui.com/ja/styled-system/theming/switch-themes#themeschemescriptを追加する
initialThemeSchemeにはconfigで設定した値を渡します。
configのカスタマイズをしていない場合は、@yamada-ui/reactからdefaultConfigをimportしてdefaultConfig.initialThemeSchemeを渡しましょう。

import { UIProvider } from "@yamada-ui/react"
import { theme, config } from "theme"

...
+<ThemeSchemeScript initialThemeScheme={config.initialThemeScheme} />
<UIProvider theme={theme} config={config}>
  <App />
</UIProvider>

ColorModeScript

ライトモード/ダークモードの切り替えでカラーモードを正常に切り替えるために、headもしくはbody内にColorModeScriptを追加する必要があります。
ColorModeScriptを追加しない場合、カラーモード切り替えのちらつきが発生します。
https://yamada-ui.com/ja/styled-system/color-mode#colormodescriptを追加する
initialColorModeにはconfigで設定した値を渡します。
configのカスタマイズをしていない場合は、@yamada-ui/reactからdefaultConfigをimportしてdefaultConfig.initialColorModeを渡しましょう。

import { UIProvider } from "@yamada-ui/react"
import { theme, config } from "theme"

...
+<ColorModeScript initialColorMode={config.initialColorMode} />
<UIProvider theme={theme} config={config}>
  <App />
</UIProvider>

NextjsのバグでApp Routerで利用する場合はクライアント側での実行とするか、サーバー側であれば以下のように一度取り出してあげる必要があります。

layout.tsx
const { initialThemeScheme, initialColorMode } = { ...config }

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="ja" data-mode="light">
      <head>
        <link rel="icon" href="/favicon.svg" />
      </head>

      <body>
        <ThemeSchemeScript initialThemeScheme={initialThemeScheme} />
        <ColorModeScript initialColorMode={initialColorMode} />
        <UIProvider config={config} theme={theme}>
          {children}
        </UIProvider>
      </body>
    </html>
  )
}

CLI

カスタマイズしたテーマのトークンの型を生成できます。
別パッケージなので、@yamada-ui/cliをインストールして利用しましょう。
https://yamada-ui.com/ja/styled-system/cli
pnpmの部分とpathは自身の環境にあわせて変更してください。
pnpm yamada-cli tokens ./src/theme/index.ts

最後に

初めて使っていただく方に、と言っておきながらだいぶ長くなってしまいました。
少しでもYamada UIの良さを伝えられていたらうれしいです。
今回はスタイリングの部分でしたが、ほかにもたくさんあります。

  • ドキュメントが日本語
  • ダークモードを考慮した設計になっている
  • アニメーションに強い
  • コンポーネント個別でインストール可能 など

https://zenn.dev/hirotomoyamada/articles/15b6f46d12841b

現在は、メンテナーの中でこんなコンポーネントあったらよくない?くらいでいろいろと作っていっているので、追加の要望やバグがあれば報告ください!
https://github.com/yamada-ui/yamada-ui/issues/new/choose

検証用に作成したリポジトリ。
https://github.com/108yen/yamada-ui-custom

GitHubで編集を提案

Discussion