Tailwind Variantsに触れてみる
Tailwind Variantsとは
Tailwind Variants a first-class variant API library for TailwindCSS.
引用元: https://www.tailwind-variants.org/docs/introduction
ドキュメントに書かれているように、Tailwind VariantsはTailwindCSSの機能とファーストクラスのVariant APIを組み合わせた技術です。TypeScriptベースで作成されているので型安全であり、特定のフレームワークに依存しないユーティリティライブラリとなっています。
ここで登場したVariant APIという言葉ですが、これはStitchesというCSS in JSライブラリの影響を受けたものであり、Tailwind Variantsはその考え方をTailwindCSSに輸入しています。
Variantsには「変異体」という意味があるようですが、Variant APIを用いることで、一つのコンポーネントに対し複数のバージョン(バリエーション)を容易に追加することができるようになります。
StitchesのVariant APIの例
Stitchesでは次のようにstyled()
を使用してvariants
を追加できます。
const Button = styled('button', {
// base styles
variants: {
variant: {
primary: {
// primary styles
},
secondary: {
// secondary styles
},
},
},
});
// Use it
<Button>Button</Button>
<Button variant="primary">Primary button</Button>
もしくはcss()
を使って次のようにも記述できます。
const button = css({
// base styles
variants: {
variant: {
primary: {
// primary styles
},
secondary: {
// secondary styles
},
},
},
});
// Use it
<div className={button()}>Button</div>
<div className={button({ variant: 'primary' })}>Primary button</div>
Tailwind Variantsに触れてみよう
TailwindCSSのセットアップ
TailwindCSSを前提としたライブラリなので、事前にTailwindCSSのセットアップが必要です。
インストール
TailwindCSSのセットアップが済んだらTailwind Variantsをインストールしましょう。
$ npm install tailwind-variants
基本のスタイル
Tailwind Variantsでは次のようにtv()
を使用してクラス名の生成を行います。
import { tv } from 'tailwind-variants';
const button = tv({
base: 'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded',
});
// button()
// => "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
export const Button = () => {
return <button className={button()}>Button</button>;
};
上記のように、第一引数に渡したオブジェクトのbase
キーが基本のスタイルとなります。
button()
の戻り値としてbase
の文字列がそのまま返ってきているように見えますが、内部的にはtailwind-mergeで処理されているので、TailwindCSSにおけるクラス名の非効率な重複を避けられるようになっています。
たとえばこれは以下のような挙動になるということです。
const style = tv({
base: 'bg-blue-500 bg-red-500',
});
style()
// => "bg-red-500"
ちなみにtwMergeをfalse
にすることで、tailwind-mergeの挙動をオフにすることも可能です。
const style = tv({
base: 'bg-blue-500 bg-red-500',
}, {
twMerge: false
});
style()
// => "bg-blue-500 bg-red-500"
Variants
VariantsはTailwind Variantsというライブラリの名前からしても、重要な機能だと考えられます。Variantsを用いることで同一のコンポーネントに対し、複数のバージョンのスタイルを容易に追加することができます。
Variantsを追加するためには、variants
キーを使用します。以下の例ではcolor
に対してprimary
とsecondary
の色を設定しています。どのvariants
を使用するかは、button({ color: 'primary' })
のように引数で指定します。
const button = tv({
base: 'text-white p-4',
variants: {
color: {
primary: 'bg-blue-500',
secondary: 'bg-red-500',
},
},
});
button({ color: 'primary' })
// => "text-white p-4 bg-blue-500"
button({ color: 'secondary' })
// => "text-white p-4 bg-red-500"
上記では色だけを設定していますが、複数のvariants
を設定することもできます。以下の例ではcolor
とsize
を設定してます。
const button = tv({
base: 'text-white p-4',
variants: {
color: {
primary: 'bg-blue-500',
secondary: 'bg-red-500',
},
size: {
small: 'text-sm p-2',
base: 'text-base p-4',
large: 'text-lg p-6',
},
},
});
button({ color: 'primary', size: 'small' })
// => "text-white bg-blue-500 text-sm p-2"
button({ color: 'secondary', size: 'large' })
// => "text-white bg-blue-500 text-base p-6"
ここでsize
に注目してみると、base
のp-4
が上書きされていることがわかります。このようにvariants
に書いたクラスは基本スタイルを上書きします。
Booleanなvariants
たとえばdisableフラグのように、booleanな値を使用してvariants
を設定することもできます。
const button = tv({
base: 'text-white p-4',
variants: {
color: {
primary: 'bg-blue-500',
secondary: 'bg-red-500',
},
// booleanなvariantsを設定
disable: {
true: 'pointer-events-none opacity-20',
},
},
});
button({ color: 'primary', disable: true });
// => "text-white p-4 bg-blue-500 pointer-events-none opacity-20"
デフォルト値の設定
variants
に対し、デフォルト値を設定したい場合はdefaultVariants
キーを使用します。
const button = tv({
base: 'text-white',
variants: {
color: {
primary: 'bg-blue-500',
secondary: 'bg-red-500',
},
size: {
small: 'text-sm p-2',
base: 'text-base p-4',
large: 'text-lg p-6',
},
},
defaultVariants: {
color: 'primary',
size: 'base',
},
});
button()
// => "text-white bg-blue-500 text-base p-4"
// defaultVariantsの値がデフォルトとなる
// base() は base({ color: 'primary', size: 'base' }) と同等になる
レスポンシブの記述
TailwindCSSでレスポンシブにおけるメディアクエリの記述を煩わしく思っている人もいるかもしれません。Tailwind Variantsでは、従来の方法に加え、次のようにTailwindCSSのブレイクポイントを扱うことができます。
const button = tv(
{
base: 'text-white',
variants: {
color: {
primary: 'bg-blue-500',
secondary: 'bg-red-500',
},
size: {
small: 'text-sm p-2',
base: 'text-base p-4',
large: 'text-lg p-6',
},
},
},
{
responsiveVariants: ['md', 'lg'],
}
);
button({
color: 'primary',
size: {
initial: 'small',
md: 'base',
lg: 'large',
},
})
// => "text-white bg-blue-500 text-sm p-2 md:text-base md:p-4 lg:text-lg lg:p-6"
Responsive variantsを扱うための前準備
Tailwind Variantsでレスポンシブなvariants
を扱いたい場合、前準備としてtailwind.config.js
を変更する必要があります。
const { withTV } = require('tailwind-variants/transformer')
/** @type {import('tailwindcss').Config} */
module.exports = withTV({
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
})
詳しくは以下のドキュメントをご覧ください。
本記事は触れませんが他にも、別のvariants
に依存したvariants
を追加できるcompoundVariants
などの機能もあります。
Slots
Slotsを使用すると、一度に分割された複数のコンポーネントに対するスタイルを作成できます。言葉だけではイメージしづらいと思うので、実際にコード例を見てみましょう。
import { tv } from 'tailwind-variants';
const card = tv({
slots: {
figure: 'w-48 p-4 border-2 border-solid bg-gray-100 text-center',
avatar: 'inline-block w-full',
caption: 'mt-2 text-gray-900 font-bold',
},
});
const { figure, avatar, caption } = card();
export const Card = () => {
return (
<figure className={figure()}>
<img
className={avatar()}
src="avatar.png"
alt="Avator"
/>
<figcaption className={caption()}>Caption</figcaption>
</figure>
);
};
/*
Result:
<figure class="'w-48 p-4 border-2 border-solid bg-gray-100 text-center">
<img src="avatar.png" alt="Avator" class="inline-block w-full">
<figcaption class="mt-2 text-gray-900 font-bold">Caption</figcaption>
</figure>
*/
また次のようにslots
とvariants
を併用して使うこともできます。
const card = tv({
slots: {
figure: 'w-48 p-4 border-2 border-solid bg-gray-100 text-center',
avatar: 'inline-block w-full',
caption: 'mt-2 text-gray-900 font-bold',
},
variants: {
color: {
light: {
figure: 'bg-white',
caption: 'text-black',
},
dark: {
figure: 'bg-black',
caption: 'text-white',
},
},
},
});
// variantsを指定
const { figure, avatar, caption } = card({ color: 'dark' });
figure()
// => "w-48 p-4 border-2 border-solid text-center bg-black"
avatar()
// => "inline-block w-full"
caption()
// => "mt-2 font-bold text-white"
本記事ではSlotsについて、これ以上言及はしませんが、Slotsの合成やレスポンシブなVariantsとの併用も行うことができます。
スタイルの上書き
class
/ className
キーでスタイルの上書きも簡単にできます。
import { tv } from 'tailwind-variants';
const button = tv({
base: 'text-white p-4 bg-blue-500',
});
button({ class: 'bg-red-500' })
// => "text-white p-4 bg-red-500"
// もしくはclassNameでも同様
button({ className: 'bg-red-500' })
// => "text-white p-4 bg-red-500"
slots
を使った場合でも、同様にclass
/ className
キーで上書きが可能です。
const card = tv({
slots: {
figure: 'w-48 p-4 border-2 border-solid bg-gray-100 text-center',
avatar: 'inline-block w-full',
caption: 'mt-2 text-gray-900 font-bold',
},
});
const { figure } = card();
figure({ class: 'text-start' });
// => "w-48 p-4 border-2 border-solid bg-gray-100 text-start"
// text-centerがtext-startに上書きされる
スタイルの拡張
extend
キーを用いてスタイルの拡張を行うこともできます。
const baseButton = tv({
base: 'p-4 bg-gray-100',
});
// extendで拡張
const successButton = tv({
extend: baseButton,
base: 'bg-green-700 text-white',
});
successButton()
// => "p-4 bg-green-700 text-white"
もしくは以下のように関数の戻り値を結合する方法でも可能です。
const baseButton = tv({
base: 'p-4 bg-gray-100',
});
const successButton = tv({
base: [baseButton(), 'bg-green-700 text-white'],
});
successButton()
// => "p-4 bg-green-700 text-white"
スタイルの拡張はbase
に限った話ではなく、variants
やslots
などのその他のキーを拡張することも可能です。以下はbase
に加えvariants
を拡張した例です。
const baseButton = tv({
base: 'p-4 bg-gray-100',
variants: {
size: {
small: 'p-2 text-sm',
large: 'p-6 text-lg',
},
},
});
const successButton = tv({
extend: baseButton,
base: 'bg-green-700 text-white',
variants: {
disable: {
true: 'opacity-20 pointer-events-none',
},
},
});
successButton({ size: 'small', disable: true });
// => "bg-green-700 text-white opacity-20 pointer-events-none p-2 text-sm"
おわりに
今後ライブラリの開発が進むにつれて、Tailwind Variantsを検討する機会が増えそうと思い、本記事を書いてみました。本記事で紹介した内容はあくまでTailwind Variantsの機能の一部にしか過ぎないので、興味がある方はぜひ使ってみてください!
参考
Discussion