🎴

もっとくわしく UnoCSS ~設定篇~

2024/08/22に公開

🙋 はじめに

ナイトウ(@engineer_naito)と申します。
今回は CSS フレームワーク Atomic CSS エンジンである UnoCSS について紹介したいと思います。

https://zenn.dev/comm_vue_nuxt/articles/what_is_unocss

こちらの記事では、UnoCSS の成り立ちや経緯を紹介し、UnoCSS の特徴について紹介しました。
今回は UnoCSS の設定について紹介したいと思います。
設定プロパティを知ることで、UnoCSS の特徴をより深く理解することができます。
また、設定プロパティの豊富さから UnoCSS の柔軟さを感じていただきたいです。

また、前回記事で作者の Anthony Fu さんからコメントをいただきました!!!

https://zenn.dev/comm_vue_nuxt/articles/what_is_unocss#comment-ba63c20e23e285

antfu さんが前回記事にコメントをくださりました

質問があれば思い切って質問してみてはいかがでしょうか 💁

📗 ドキュメント

https://unocss.dev/

https://github.com/unocss/unocss

🛝 公式 Playground

https://unocss.dev/play/

公式 Playground が提供されています。
こちらを活用して、UnoCSS を体験してみてください。

また、この記事内でサンプルコード付きの公式 Playground へのリンクを用意しています。
(e.g. 公式 Playground 👈)
ぜひ実際にコードを動かして UnoCSS を体験しながら記事を読んでください 👍

1️⃣ UnoCSS とは

UnoCSS 公式ドキュメントトップページ のスクショ

Instant On-demand Atomic CSS Engine

Customizable · Powerful · Fast · Joyful

UnoCSS は、オンデマンドで動作する Atomic CSS エンジンです。
Tailwind CSS, Windi CSS の影響を受け、より迅速柔軟なスタイル定義を可能にするために設計されました。

🤗 UnoCSS の例

公式 Playground

<div m-10>Hello</div>
uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [["m-10", { margin: "10px" }]],
});

上記からは

.m-10 {
  margin: 10px;
}

という CSS が生成されます。

UnoCSS 公式Playground での例のスクショ

✨ UnoCSS の特徴(Configuration)

UnoCSS の大きな特徴の一つは、必要な機能やスタイルを設定ファイルによって柔軟に選択できる点です。
(不要な機能を使わないということでもあります。)

プロジェクトごとに最適なスタイルやユーティリティを選択でき、不要な CSS が生成されることを防ぎます。
必要なプリセットやプラグインを指定することで、プロジェクトに合わせたカスタム CSS 環境を簡単に構築することができます。

設定ファイルは専用のファイル uno.config.ts を用いることが推奨されています。

設定項目 説明
ルール (Rules) Atomic CSS ユーティリティを定義
ショートカット (Shortcuts) 複数のルールを 1 つのショートハンドに
テーマ (Theme) テーマ変数を定義
バリアント (Variants) ルールにカスタムなコンベンションを適用
エクストラク (Extractors) ユーティリティの使用場所と抽出方法を定義
プリフライト (Preflights) グローバルな生の CSS を定義
レイヤー (Layers) 各ユーティリティレイヤーの順序を定義
プリセット (Presets) 汎用的な事前定義された設定
トランスフォーマー (Transformers) コンベンションをサポートするためにソースコードを変換
オートコンプリート (Autocomplete) カスタマイズされたオートコンプリート候補を定義


順番に設定項目について見ていきましょう。

📏 ルール

🤫 静的ルール

ルールによってユーティリティクラスと生成される CSS のペアを定義できます。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    ["m-1", { margin: "0.25rem" }],
    ["font-bold", { "font-weight": 700 }],
  ],
});
.m-1 {
  margin: 0.25rem;
}

.font-bold {
  font-weight: 700;
}

🗣️ 動的ルール

正規表現と関数を用いて動的にユーティリティを定義することができます。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
    [/^p-(\d+)$/, match => ({ padding: `${match[1] / 4}rem` })],
  ],
});
<div class="m-100">
  <button class="p-3">My Button</button>
</div>

では、以下の CSS が生成されます。

.m-100 {
  margin: 25rem;
}

.p-5 {
  padding: 1.25rem;
}

☕️ ルールの順序

UnoCSS は生成された CSS 内で定義されたルールの順序を尊重します。
後に定義されたものがより高い優先度を持ちます。

動的に定義されるルールについてはアルファベット順に生成されます。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules:[
    [/^text-(.+)$/, ([, color]) => ({
      color: `${color}`,
    })],
  ]
});
<div class="text-red text-green text-blue ">Hello</div>

からは以下の順で CSS が生成されます。

.text-blue {
  color: blue;
}

.text-green {
  color: green;
}

.text-red {
  color: red;
}

🤝 ルールのマージ

UnoCSS では生成される CSS のサイズを小さくするために、CSS をマージします。

<div class="m-2 hover:m2"></div>
.hover\:m2:hover,
.m-2 {
  margin: 0.5rem;
}

🛠️ プリセット

UnoCSS は Atomic CSS エンジンであり、コアユーティリティを提供していません。
上記の例のように、自らルールを設定することができます。

そして、複数のルールをプリセットとして切り出すことができます。
これが UnoCSS の一番の特徴です。

プリセットは特定のユースケースに合わせたスタイルの管理を簡単にするための設定の集合です。
プリセットを使用することで、共通の設定やスタイルをプロジェクト全体で一貫して利用できるようになります。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    [/^m-([.\d]+)$/, ([_, num]) => ({ margin: `${num}px` })],
    [/^p-([.\d]+)$/, ([_, num]) => ({ padding: `${num}px` })],
  ],
});
my-preset.ts
import { Preset } from "unocss";

export const myPreset: Preset = {
  name: "my-preset",
  rules: [
    [/^m-([.\d]+)$/, ([_, num]) => ({ margin: `${num}px` })],
    [/^p-([.\d]+)$/, ([_, num]) => ({ padding: `${num}px` })],
  ],
  // ...
};
uno.config.ts
import { defineConfig } from "unocss";
import { myPreset } from "./my-preset";

export default defineConfig({
  presets: [
    myPreset,
  ],
});

また、オプションを受け取ることもできます。

my-preset.ts
import { definePreset } from "unocss";
import type { Preset } from "unocss";

interface MyPresetOptions {
  primaryColor?: string;
  secondaryColor?: string;
}

export default definePreset((options?: MyPresetOptions): Preset => {
  return {
    name: "my-preset",
    rules: [
      ["bg-primary", { "background-color": options?.primaryColor || "blue" }],
      ["bg-secondary", { "background-color": options?.secondaryColor || "green" }],
    ],
  };
});
uno.config.ts
import { defineConfig } from "unocss";
import myPreset from "./my-preset";

export default defineConfig({
  presets: [
    myPreset({
      primaryColor: "#42B983",
      secondaryColor: "#35495E",
    }),
  ],
});

🏃 ショートカット

複数のルールを組み合わせてひとつのショートカットを定義できます。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  shortcuts: {
    // 複数のユーティリティのショートカット
    "btn": "py-2 px-4 font-semibold rounded-lg shadow-md",
    "btn-green": "text-white bg-green-500 hover:bg-green-700",
    // ひとつのユーティリティに対するエイリアスショートカット
    "red": "text-red-100",
  }
});

ルールと同様に動的なショートカットも定義できます。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  shortcuts: [
    { btn: "py-2 px-4 font-semibold rounded-lg shadow-md" },
    [/^btn-(.*)$/, ([, c]) => `bg-${c}-400 text-${c}-100 py-2 px-4 rounded-lg`],
  ],
});

💐 テーマ

UnoCSS でも Tailwind CSS, Windi CSS でおなじみのテーマ化システムをサポートしています。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  theme: {
    colors: {
      "very-cool": "#0000FF",
      brand: {
        primary: "hsl(var(--hue, 217) 78% 51%)",
      },
    },
  },
  rules: [
    [
      /^text-(.*)$/,
      ([, c], { theme }) => {
        if (theme.colors[c]) return { color: theme.colors[c] }
      },
    ],
  ],
});

ブレークポイントの設定もできます。

公式 Playground

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  theme: {
    breakpoints: {
      sm: "320px",
      md: `${40 * 16}px`,
      lg: "960px",
    },
  },
});

🫚 バリアント

バリアントを用いることで既存のルールにバリエーションを適用することができます。
(Tailwind CSS の hover: バリアントのように)

import { defineConfig } from "unocss";

export default defineConfig({
  variants: [
    // hover:
    (matcher) => {
      if (!matcher.startsWith("hover:")) return matcher;
      return {
        // プレフィックス `hover:` (6文字)を取り除く
        matcher: matcher.slice(6),
        selector: (s) => `${s}:hover`,
      };
    },
  ],
  rules: [[/^m-(\d)$/, ([, d]) => ({ margin: `${d / 4}rem` })]],
});
`hover:m-2` の場合の流れ ⏬️
  1. hover:m-2 が抽出される
  2. hover:m-2 が判定のために全てのバリアントに送られる
  3. hover:m-2 がマッチし、m-2 を返す
  4. m-2 がさらに次のバリアントの判定のために用いられる
  5. 他にマッチしなければ m-2 がルールの判定に渡される
  6. 最初のルールにマッチし、.m-2 { margin: 0.5rem; } が生成される
  7. 最終的に以下の CSS となる
.hover\:m-2:hover {
  margin: 0.5rem;
}

ユーザがホバーしたときにだけ m-2 を適用することが実現できます。

🥤 エクストラクタ

ソースコード中のユーティリティを抽出するためにエクストラクタが使われます。

デフォルトでは extractorSplit が適用されています。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  extractors: [
    // 独自のエクストラクタ
  ],
  // デフォルトのエクストラクタを無効
  extractorDefault: false,
  // デフォルトのエクストラクタを独自のものに上書き
  extractorDefault: myExtractor,
});

🤖 トランスフォーマー

トランスフォーマーはソースコード変形のための統一されたインターフェースを提供します。

my-transformer.ts
import { createFilter } from "@rollup/pluginutils";
import { SourceCodeTransformer } from "unocss";

interface MyOptions {
  // カスタムオプションを定義
};

export default function myTransformers(options: MyOptions = {}): SourceCodeTransformer {
  return {
    name: "my-transformer",
    enforce: "pre",
    idFilter(id) {
      return id.match(/\.[tj]sx$/);
    },
    async transform(code, id, { uno }) {
      code.appendRight(0, "/* transformed by my-transformer */");
    },
  };
};
uno.config.ts
import { defineConfig } from "unocss";
import { myTransformers } from "./my-transformers";

export default defineConfig({
  transformers: [myTransformers()],
});

では、UnoCSS がビルドプロセス中に .tsx または .jsx ファイルを処理するときに自動的に変換が実行されます。

例えば、

export const App = () => {
  return <div>Hello World</div>;
};

は、

/* transformed by my-transformer */
export const App = () => {
  return <div>Hello World</div>;
};

と変換されます。

例えば、複数のクラスを 1 つのクラスにコンパイルするトランスーフォーマー transformer-compile-class では、

<div class=":uno: text-center sm:text-left">
  <div class=":uno: text-sm font-bold hover:text-red"></div>
</div>

が、

<div class="uno-qlmcrp">
  <div class="uno-0qw2gr"></div>
</div>
.uno-qlmcrp {
  text-align: center;
}

.uno-0qw2gr {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
}

.uno-0qw2gr:hover {
  --un-text-opacity: 1;
  color: rgb(248 113 113 / var(--un-text-opacity));
}

@media (min-width: 640px) {
  .uno-qlmcrp {
    text-align: left;
  }
}

のようにコンパイルされます。

✈️ プリフライト

生の CSS をプリフライトとして設定から注入することができます。
これによりデフォルトスタイルの指定や CSS リセットを行うことができます。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  preflights: [
    {
      getCSS: ({ theme }) => `
        * {
          color: ${theme.colors.gray?.[700] ?? "#333"};
          padding: 0;
          margin: 0;
        }
      `,
    },
  ],
});

📚 レイヤー

CSS の順序は優先順位に影響します。
エンジンはルールの順序を保持しますが、ユーティリティをグループ化してその順序を明示的に制御したい場合もあります。

Tailwind CSS は 3 つの固定レイヤー(base, components, utilities)を持っています。
それに対して UnoCSS では、自由 にレイヤーを定義することができます。

レイヤーを定義したい場合はルールの 3 番目の要素としてメタデータを指定できます。
省略すれば default となります。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    [/^m-(\d)$/, ([, d]) => ({ margin: `${d / 4}rem` }), { layer: "utilities" }],
    ["btn", { padding: "4px" }],
  ],
});

というルールからは以下の順の CSS が生成されます。

/* layer: default */
.btn {
  padding: 4px;
}

/* layer: utilities */
.m-2 {
  margin: 0.5rem;
}

レイヤーの順序も制御することができます。
(順序を指定しない場合はレイヤー名の辞書順になります。)

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  layers: {
    "components": -1,
    "default": 1,
    "utilities": 2,
    "my-layer": 3,
  },
});

カスタム CSS をレイヤー間に挿入したい場合はエントリーモジュールを更新します。

// "uno:[layer-name].css"
import "uno:components.css";

// "components" と "utilities" 以外のレイヤーがここにフォールバックされます
import "uno.css";

// カスタムCSS
import "./my-custom.css";

// "utilities" レイヤーが最も優先されます
import "uno:utilities.css";

🍰 CSS カスケードレイヤー

https://developer.mozilla.org/ja/docs/Web/CSS/@layer

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  outputToCssLayers: true,
});

とすることで、CSS カスケードレイヤー付きの CSS を生成することができます。

レイヤー名を変更することもできます。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  outputToCssLayers: {
    cssLayerName: (layer) => {
      if (layer === "default") {
        return "utilities";
      }
      if (layer === "shortcuts") {
        return "utilities.shortcuts";
      }
      return layer;
    }
  },
});

✅ オートコンプリート

VS Code 拡張機能の自動補完をカスタマイズすることができます。
(静的なルールでは何も設定しなくても自動補完が効きます。)

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    [
      /^custom-autocomplete-m-(\d+)$/,
      ([, d]) => ({ margin: `${d / 4}rem` }),
      { autocomplete: "custom-autocomplete-m-<num>" },
    ],
  ],
});

公式 Playground

autocomplete 設定を用いることでより具体的なテンプレートや短縮記法を定義することもできます。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  autocomplete: {
    templates: [
      // テーマから推測されるテンプレート
      "bg-$color/<opacity>",
      // 省略形
      "text-<font-size>",
      // 論理 OR グループ
      "(b|border)-(solid|dashed|dotted|double|hidden|none)",
      // 定数
      "w-half",
    ],
    shorthands: {
      // `opacity: "(0|10|20|30|40|50|60|70|90|100)"` と同じ意味
      "opacity": Array.from({ length: 11 }, (_, i) => i * 10),
      "font-size": "(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)",
      // 組み込みの省略形を上書き
      "num": "(0|1|2|3|4|5|6|7|8|9)",
    },
  },
});

💭 まとめ

UnoCSS は、オンデマンド型の Atomic CSS エンジンとして、スタイル定義を革新する強力なツールです。

  • 柔軟性: プロジェクトに合わせたスタイルや機能のカスタマイズが可能
  • 効率性: 必要な部分だけを設定することで、無駄のないスタイル管理が実現
  • ルール設定:
    • 静的ルール: 決まったスタイルを簡単に設定
    • 動的ルール: 条件に応じたスタイルを柔軟に定義
  • プリセットとテーマ: 共通のスタイルをプロジェクト全体で一貫して使用可能
  • ショートカットとバリアント: 再利用性の高いスタイルを手軽に設定
  • カスケードレイヤー管理: CSS の優先順位を細かく制御できる

様々な設定プロパティを活用することで、パフォーマンスを向上させるだけでなく、スタイル管理も大幅に楽になります。

💚 最後に

この記事の執筆にあたって GANGAN さんにレビューしていただき、たくさんのアドバイスをもらいました。
本当にありがとうございます。

https://github.com/shinGangan/comm_vue_nuxt/issues/62


この記事を通して、UnoCSS の設定の豊富さと柔軟性についてご理解いただけたでしょうか。
UnoCSS を使うことでよりシンプルで効率的なスタイル定義が可能になります。
この記事が UnoCSS を使いこなす手助けになれば幸いです。

UnoCSS の魅力をもっと知りたい方は公式サイトやコミュニティリソースを活用して、積極的に情報を収集してみてください。

今後 UnoCSS ユーザがもっと増えて、日本語情報が増えることを強く願っています。
一緒にコミュニティを盛り上げていきましょう 💪

最後まで読んでいただきありがとうございました!

GitHubで編集を提案
Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion