Open1

vanilla-extract探訪

kaorun343kaorun343

@vanilla-extract/sprinkles について

@vanilla-extract/sprinkles というライブラリを使うと、tailwindみたいなユーティリティCSSを組み合わせたスタイリングを実現できる。

https://vanilla-extract.style/documentation/packages/sprinkles/

properties

properties フィールドのみを用いたコード例はこんな感じである。

import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'

const colorProperties = defineProperties({
  properties: {
    color: ['red', 'green', 'blue'],
    backgroundColor: ['cyan', 'magenta', 'yellow'],
  }
})

export const sprinkles = createSprinkles(colorProperties)

するとこのようなCSSを生成する。(セレクタは説明のためにわかりやすくした)。
1つのプロパティに渡された値ごとにスタイルを生成する。

.sprinkle-color-red1 {
  color: red;
}
.sprinkle-color-green1 {
  color: green;
}
.sprinkle-color-blue1 {
  color: blue;
}
.sprinkle-backgroundColor-cyan1 {
  background-color: cyan;
}
.sprinkle-backgroundColor-magenta1 {
  background-color: magenta;
}
.sprinkle-backgroundColor-yellow1 {
  background-color: yellow;
}

sprinkles 関数を利用すると、指定したスタイルに必要なクラス名をまとめた文字列を生成してくれる。

import { sprinkles } from '~/sprinkles.css.ts'

export const redText = sprinkles({
  // `colorProperties` の `color` プロパティに `red` が入っているので指定できる
  color: 'red',
  // `colorProperties` の `backgroundColor ` プロパティに `yellow ` が入っているので指定できる
  backgroundColor: 'yellow',
})

上記の redText には ".sprinkle-color-red1 .sprinkle-backgroundColor-yellow1 が入る。

@vanilla-extract/cssstyle 関数を用いる場合と比較すると、 クラス名が長くなる代わりにCSSが小さくなる。Tailwindと同様に、同じようなスタイルを利用するのであればCSSが増えにくいはずである。

一方で、Tailwindはデフォルトで様々な値が用意されているのに対して、vanilla-extractのsprinklesでは必要な値を自分で指定しなければならない。もちろんTailwindをしっかり使う場合には自分で色情報だったりサイズだったりを指定することになるとは思う。

また、指定していないプロパティや値を渡そうとすると型チェックで弾いてくれる。

export const redBackground = sprinkles({
  // @ts-expect-error 未定義のプロパティに値を設定することはできない
  borderColor: 'red',
})

export const yellowText = sprinkles({
  // @ts-expect-error  `sprinkles` に渡した `color` プロパティで未定義の値は指定できない
  color: 'yellow',
})

shorthands

例えば padding は、tailwindだと pl-2 のように1つずつ指定する方法もあれば、p-2px-2 のようにまとめて指定方法がある。これと同等の機能を shorthands プロパティで実現できる。

const sizes = [4, 8, 12] as const


const sizePropeties = defineProperties({
  properties: {
    paddingTop: sizes,
    paddingRight: sizes,
    paddingBottom: sizes,
    paddingLeft: sizes,
  },
  shorthands: {
    padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
    paddingX: ['paddingRight', 'paddingLeft'],
  },
})

このように設定することで、 sprinkles({ padding: 8 })sprinkles({ paddingX: 12 }) のようにして複数項目を一括指定できる。

Tailwindと比較すると、Tailwindは p-2px-2 では異なるスタイル定義になるのに対し、vanilla-extractではアトミックなCSSを組み合わせて実現する点で異なっている。

conditions, defaultCondition

conditionsを使えばメディアクエリや疑似クラスごとに異なる値を指定できるようになる。
キーはわかりやすい名前をつけておくとよさそう。

const colorProperties = defineProperties({
  conditions: {
    default: {},
    hover: { selector: '&:focus' },
  },
  defaultCondition: 'default',
  properties: {
    color: ['red', 'green', 'blue'],
    backgroundColor: ['cyan', 'magenta', 'yellow'],
  }
})

const 

すると以下のようなCSSを生成する。コンディションごとに別々のクラス名を生成するようである。

.sprinkle-color-red1,
.sprinkle-color-red2:hover {
  color: red;
}
.sprinkle-color-green1,
.sprinkle-color-green2:hover {
  color: green;
}
.sprinkle-color-blue1,
.sprinkle-color-blue2:hover {
  color: blue;
}
.sprinkle-backgroundColor-cyan1,
.sprinkle-backgroundColor-cyan2:hover {
  background-color: cyan;
}
.sprinkle-backgroundColor-magenta1,
.sprinkle-backgroundColor-magenta2:hover {
  background-color: magenta;
}
.sprinkle-backgroundColor-yellow1,
.sprinkle-backgroundColor-yellow2:hover {
  background-color: yellow;
}

そして、 sprinkles({ color: { default: 'green', hover: 'blue' } }) と書くと、2つのクラス名を合体させた文字列を得られる。

セレクタ以外にもメディアクエリやコンテナクエリなどを条件に指定できる。
Tailwindにおける hover:md: といったプレフィクスを自前で用意するに相当すると言える。