vanilla-extract の recipe を活用する


こんにちは、上條(mk-0A0)です。
最近、「サイズは同じだけどページによって色が異なる」というよくあるボタンのデザインに遭遇しました。色の指定のためだけに新しくクラスを定義するのはちょっとめんどくさいな〜と思っていたのですが、vanilla-extract の recipe を使うことでより良い実装にできたので共有したいと思います。

vanilla-extract とは

vanilla-extract とは、TypeScript で記述できる CSS ライブラリです。
型安全なので開発体験の向上につながるのと、ゼロランタイムを謳っているためパフォーマンスの向上にも期待できます。

過去に弊社ブログでも記事になっているので、ぜひこちらもご覧ください。
型安全な CSS Modules?「vanilla-extract」

recipe とは

vanilla-extract で使えるの機能の一つで、複数のスタイルをまとめることができます。スタイルの出し分けが必要なときに便利です。
Recipes – vanilla-extract

vanilla-extract だけをインストールした状態では使用できないので、別途インストールが必要になります。

npm install @vanilla-extract/recipes

使い方

サンプルとしてボタンのスタイルを作成してみます。
Style.css.ts を作成して Button を定義します。vanilla-extract から recipe 関数を import し、関数内でオブジェクトを定義します。
recipe で主に使うオプションは以下2つです。

base:すべての Button に共通するスタイルを定義する
variants:複数パターンがあるスタイルを定義する

このサンプルではボタンの背景色とテキストカラーが black か white 2つのパターンがあります。

import { recipe } from '@vanilla-extract/recipes'

const Button = recipe({
  base: {
    width: '200px',
    height: '80px'
  },
  variants: {
    backgroundColor: {
      black: { backgroundColor: '#000000' },
      white: { backgroundColor: '#EEEEEE' },
    },
    textColor: {
      black: { color: '#000000' },
      white: { color: '#FFFFFF' },
    },
  },
})

実際にこの Button を使用すると以下のようになります。backgroundColor には black、textColor にはwhite を指定しました。

import { Button } from "./Style.css"

const Home = () => {
  return (
    <button className={Button({ backgroundColor: 'black', textColor: 'white' })}>
      Button
    </button>
  )
}

export default Home

ちなみに backgroundColor に white、textColor に black を指定するとこのようになります。

import { Button } from "./Style.css"

const Home = () => {
  return (
    <button className={Button({ backgroundColor: 'white', textColor: 'black' })}>
      Button
    </button>
  )
}

export default Home

また、variants は boolean での判定も可能です。
角丸の有無を出し分けるスタイル rounded を boolean で作成すると以下のようになります。

import { recipe } from '@vanilla-extract/recipes'

const Button = recipe({
  base: {...},
  variants: {
    backgroundColor: {...},
    textColor: {...},
    rounded: {
      true: {
        borderRadius: 999,
      },
    },
  },
})
import { Button } from "./Style.css"

const Home = () => {
  return (
    <button className={Button({ backgroundColor: 'black', textColor: 'white', rounded: true })}>
      Button
    </button>
  )
}

export default Home

なんとなく recipe というものが掴めてきたでしょうか?

他のオプション

base variants 以外にもオプションがあるので、それらを紹介します。

defaultVariants

設定した variants の中からデフォルトで当てたいスタイルを指定できます。defaultVariants を設定しておくと、そのスタイルを使用する場合は tsx 側でのオプション設定が不要になります。

import { recipe } from '@vanilla-extract/recipes'

const Button = recipe({
  base: {...},
  variants: {...},
  defaultVariants: {
    backgroundColor: 'black',
    textColor: 'white',
  },
})
import { Button } from "./Style.css"

const Home = () => {
  return (
    // backgroundColor: 'black', textColor: 'white'
    <button className={Button()}>Button</button>
  )
}

export default Home

compoundVariants

特定の variants が指定されたときのスタイルを定義します。以下サンプルの場合、backgroundColor が white、 textColor が black、 rounded が true のときに border が設定されます。

import { recipe } from '@vanilla-extract/recipes'

const Button = recipe({
  base: {...},
  variants: {...},
  defaultVariants: {...},
  compoundVariants: [
    {
      variants: {
        backgroundColor: 'white',
        textColor: 'black',
        rounded: true,
      },
      style: {
        border: '1px solid #000',
      },
    },
  ],
})
import { Button } from "./Style.css"

const Home = () => {
  return (
    <button className={Button({ backgroundColor: 'white', textColor: 'black', rounded: true })}>
      Button
    </button>
  )
}

export default Home

メリット

一つのクラスでここまで柔軟にスタイルを当てられるのは大きなメリットです。無理にすべてのスタイルを集約しようとすると煩雑になって逆に使いづらくなってしまいそうですが、適度な粒度であれば開発体験の向上につながるのではないでしょうか。

まとめ

recipe はスタイルだけの集合体が作れます。そのスタイルのパターンが豊富な場合はスタイルを当てるだけの tsx コンポーネントを作ることもあると思いますが、そうなったとしてもきれいに記述できるので良いと思いました。
今までよく分かってなかった compoundVariants の使い方をようやく理解できたので、実際に使用できるところがないかコードを見直したいです。

Gaji-Laboでは、 Next.js 経験が豊富なフロントエンドエンジニアを募集しています

弊社では Next.js の知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違う Gaji-Labo を味わいに来ませんか?

Next.js の設計・実装を得意とするフロントエンドエンジニア募集要項

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!


投稿者 Kamijo Momoka

フロントエンドエンジニア。
HTML/CSS/JavaScript/WordPressでのサイト制作からNext.js/TypeScriptなどを使ったWebアプリ開発、FigmaでのUIデザインまで広く経験しています。 デザインエンジニアと名乗るのが夢です。