vanilla-extractでnormalize.cssを設定する

profile image

vanilla-extractを使用してプロジェクトに直接normalize.cssを設定する方法

この記事は Jetbrains's Coding Agent Junie junie logoによって翻訳されました。誤訳があれば教えてください!

vanilla-extractをプロジェクトに導入してみて、一つ惜しい点を見つけた。

Tailwind CSS v4はデフォルトでpreflightというnormalizeスタイルが含まれているため、特別な設定なしでブラウザのデフォルトスタイルが正規化されるが、vanilla-extractにはそのような機能がない。

そこで、自分で作ることにした。

modern-normalizeを参考にして作成

Tailwindのpreflightがベースにしているmodern-normalizeを参考にした。

modern-normalizeは、ブラウザ間のスタイルの不一致を最小限に抑えつつ、不要なスタイルの削除は最大限に控えるという方針で設計されたライブラリだ。

CSS Cascade Layersの活用

normalizeスタイルは、他のスタイルよりも優先順位を低くする必要がある。CSS Cascade Layers(以下、レイヤー)を使用すると、スタイル間の優先順位を明示的に管理できるため、normalizeスタイルが意図せず他のスタイルを上書きしてしまう問題を防止できる。

vanilla-extractのglobalLayerでレイヤーをまず定義した。globalLayerはレイヤー名を引数として受け取り、レイヤーを作成し、生成されたレイヤーの参照値を返す。

公式ドキュメントでは、この参照値をlayers.css.tsからexportしておき、スタイルを記述する際にレイヤー名の文字列の代わりに参照値を使用することを推奨している。レイヤー名を文字列で直接書くとタイポに気づきにくく、入れ子になったレイヤーのように名前が自動的に計算される場合には、参照値を使うことで正確な名前が保証されるからだ。

typescript
// layers.css.ts
import { globalLayer } from "@vanilla-extract/css";

export const normalize = globalLayer("normalize");

normalize.cssの記述

vanilla-extractでグローバルセレクタにスタイルを適用するときは、globalStyleを使用する。第一引数にセレクタ文字列を、第二引数にスタイルオブジェクトを受け取る。

typescript
import { globalStyle } from "@vanilla-extract/css";
import { normalize } from "./layers.css.ts";

/**
 * CSS Normalize based on modern-normalize v3.0.1
 * https://github.com/sindresorhus/modern-normalize
 * MIT License
 */

/*
Document
========
*/

/**
より良いボックスモデルの使用 (opinionated)。
*/
globalStyle("*, ::before, ::after", {
  "@layer": {
    [normalize]: {
      boxSizing: "border-box",
    },
  },
});

/**
1. すべてのブラウザで一貫したデフォルトフォントを使用
2. すべてのブラウザで正しい line height を設定
3. iOS での方向転換後のフォントサイズ調整を防止
4. より読みやすい tab size を使用 (opinionated)
*/
globalStyle("html", {
  "@layer": {
    [normalize]: {
      fontFamily:
        'system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
      lineHeight: 1.15,
      WebkitTextSizeAdjust: "100%",
      tabSize: 4,
    },
  },
});

/*
Sections
========
*/

/**
すべてのブラウザで margin を削除
*/
globalStyle("body", {
  "@layer": {
    [normalize]: {
      margin: 0,
    },
  },
});

/*
Text-level semantics
====================
*/

/**
Chrome と Safari で正しいフォントの太さを追加
*/
globalStyle("b, strong", {
  "@layer": {
    [normalize]: {
      fontWeight: "bolder",
    },
  },
});

/**
1. すべてのブラウザで一貫したデフォルトフォントを使用
2. すべてのブラウザでおかしな 'em' フォントサイズを修正
*/
globalStyle("code, kbd, samp, pre", {
  "@layer": {
    [normalize]: {
      fontFamily:
        'ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace',
      fontSize: "1em",
    },
  },
});

/**
すべてのブラウザで正しいフォントサイズを追加
*/
globalStyle("small", {
  "@layer": {
    [normalize]: {
      fontSize: "80%",
    },
  },
});

/**
すべてのブラウザで 'sub' と 'sup' 要素が line height に影響を与えないように防止
*/
globalStyle("sub, sup", {
  "@layer": {
    [normalize]: {
      fontSize: "75%",
      lineHeight: 0,
      position: "relative",
      verticalAlign: "baseline",
    },
  },
});

globalStyle("sub", {
  "@layer": {
    [normalize]: {
      bottom: "-0.25em",
    },
  },
});

globalStyle("sup", {
  "@layer": {
    [normalize]: {
      top: "-0.5em",
    },
  },
});

/*
Tabular data
============
*/

/**
Chrome と Safari でテーブルの border 色の継承を修正
*/
globalStyle("table", {
  "@layer": {
    [normalize]: {
      borderColor: "currentcolor",
    },
  },
});

/*
Forms
=====
*/

/**
1. すべてのブラウザでフォントスタイルを変更
2. Firefox と Safari で margin を削除
*/
globalStyle("button, input, optgroup, select, textarea", {
  "@layer": {
    [normalize]: {
      fontFamily: "inherit",
      fontSize: "100%",
      lineHeight: 1.15,
      margin: 0,
    },
  },
});

/**
iOS と Safari でクリック可能なタイプのスタイルを修正
*/
globalStyle('button, [type="button"], [type="reset"], [type="submit"]', {
  "@layer": {
    [normalize]: {
      WebkitAppearance: "button",
    },
  },
});

/**
すべてのブラウザで、開発者が 'fieldset' 要素の値を 0 に設定したときに混乱しないよう padding を削除
*/
globalStyle("legend", {
  "@layer": {
    [normalize]: {
      padding: 0,
    },
  },
});

/**
Chrome と Firefox で正しい垂直整列を追加
*/
globalStyle("progress", {
  "@layer": {
    [normalize]: {
      verticalAlign: "baseline",
    },
  },
});

/**
Safari で増加/減少ボタンのカーソルスタイルを修正
*/
globalStyle("::-webkit-inner-spin-button, ::-webkit-outer-spin-button", {
  "@layer": {
    [normalize]: {
      height: "auto",
    },
  },
});

/**
1. Chrome と Safari でおかしな外観を修正
2. Safari で outline スタイルを修正
*/
globalStyle('[type="search"]', {
  "@layer": {
    [normalize]: {
      WebkitAppearance: "textfield",
      outlineOffset: "-2px",
    },
  },
});

/**
macOS の Chrome と Safari で内部の padding を削除
*/
globalStyle("::-webkit-search-decoration", {
  "@layer": {
    [normalize]: {
      WebkitAppearance: "none",
    },
  },
});

/**
1. iOS と Safari でクリック可能なタイプのスタイルを修正
2. Safari でフォント属性を 'inherit' に変更
*/
globalStyle("::-webkit-file-upload-button", {
  "@layer": {
    [normalize]: {
      WebkitAppearance: "button",
      font: "inherit",
    },
  },
});

/*
Interactive
===========
*/

/**
Chrome と Safari で正しい display を追加
*/
globalStyle("summary", {
  "@layer": {
    [normalize]: {
      display: "list-item",
    },
  },
});

GitHub Gistにもアップロードした! -> Modern CSS Normalize with Vanilla Extract & CSS Layers

global.css.ts

最後に、レイヤー定義ファイルを一番最初に import しなければならない。CSS レイヤーは宣言順序が優先順位に直接影響するため、順序が保証されないと normalize スタイルが意図した通りに動作しない可能性がある。

typescript
// global.css.ts
import "./layers.css"; // 必ず一番最初に
import "./normalize.css";

あとはプロジェクトのエントリーポイントで global.css ファイルを import するだけだ!

ts
import "../global.css";

参考