UnoCSSのバグと Chromeのvar()に注意【OSS入門】

## 要約
- UnoCSSでバグを発見した：  `drop-shadow-xxx` などのクラスがスタイルに反映されない
- こちらでissueおよびPRを提出し現在は修正済
- Chromeでは `var()` の中身を省略表示することがある




## UnoCSSとは

「Atomic CSS エンジン」
＝必要とされるスタイルだけ動的に生成する

https://unocss.dev/

::: note
- TailwindCSS V4用のプリセット ([presetWind4](https://unocss.dev/presets/wind4)) も利用できる
- [PlayGround](https://unocss.dev/play/#version=66.1.0-beta.7)でおためし
:::




## バグ発覚

### 一休 Frontend Meetup での発表

2025年2月20日に株式会社一休さん主催のイベント 一休 Frontend Meetup が開催される

https://user-first.ikyu.co.jp/entry/2025/02/20/101906

自社WebComponentsの開発に UnoCSS を利用していることが発表される


|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/d46e64a8-b501-4382-9a6f-5277297ea351.png)|
<font color="gray" size="sm">『一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜』より</font>


### サンプルアプリでのUnoCSS試用

上記講演を受けて[Vite + React](https://vite.dev/guide/)環境でUnoCSS (66.1.0-beta.6) を試用することにした

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/d3764541-0900-4617-b3e3-f8aced893963.png)|
<font color="gray" size="sm">index.htmlではアイコンのホバー中に影が描画される</font>

するとpresetWind4利用時classNameに[`drop-shadow-xxx`](https://tailwindcss.com/docs/filter-drop-shadow)を指定してもシャドーが描画されないことを確認


|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/a00a49fc-4ec9-406b-b66d-13b9e9ece396.png)|
<font color="gray" size="sm">TailwindCSS V4 （右側）では問題なくシャドーが描画される</font>

:::note
PlayGround

[https://unocss.dev/play/#version=66.1.0-beta.6&html=...](https://unocss.dev/play/#version=66.1.0-beta.6&html=DwQwTgLglgxgNgUwATxAZzQXgEQQQDwgFoB3ACyj2wD4AoJJYAQiKKQBMwB7AByLTIh2XEkQDaABgD60gMwBWHvilgA5gCMQACgkAaPQCZ58-QDp5ASgC6SVnQbA0CGNC4A7FHHRZs61aQo8JD4ADiQAM0R8JFUQPgBGCRp6BkZ2KAA3FNTPbxw-InUvGABrDm4%2BASERcWlpRWU1TR19XSMTCXNrbGykexzy3n5BYRIkdQBPJABVNy4AYQBlRd7gAHp0jP6HTd6GVAx8-yKQUp6BpDQICcQcVgBXNyJOIarRgC5BypGRHSQJJANJAZcBaB5PF7faqiGBcOBcMC6JBNbR6QzGMyWCwWADc4SgcDwYE%2BILAYKIj0KcHuiIswNB4MKYCgqjIEDcCAwujppPJlNhbggYHQEG59LJjNUwomaBgIEQYt5jLI9wQRG4EBAeEVDIpTygbgyCEgOolev4WppWoQpr5TycPCgIFtjMhw2h3Jx51S21SbreInGUwAKiACSQDewliscutNv11k4XFB3HR1uBoPAEHQgA&config=JYWwDg9gTgLgBAbwFBzgEwKYDNgDsMDCEuOA5gDQpxhQYDOGMA6nmgCxIC%2BcWUEIcAOQBXXBADGdOoKRIMAD0ix02AIbCANvEw58REsFIAKZKhr1GdAFxwA2lTO0GzVmyMBKSqgC6lTu6QgA&css=PQKgBA6gTglgLgUzAYwK4Gc4HsC2YDCAyoWABYJQIA0YAhgHYAmYcUD6AZllDhWOqgAOg7nAB0YAGLcwCAB60cggDYIAXGBDAAUKDBi0mXGADe2sGC704AWgDuCGAHNScDQFYADJ4Dc5sAACtMLKAJ5gggCMLPK2ABR2pPBIcsoAlH4WAEa0yADWTlBYqEw2yFjK3Bpw5LxxAOTllVDoYpSMYgAs3vUZ2gC%2BmsBAA&options=N4XyA)

:::



## バグの原因

根本原因は <b>CSS変数のフォールバック</b> が設定されていないことだった


### フォールバックとは

`var()` の第二引数のこと

※ フォールバックは特に正式名称ではない

https://developer.mozilla.org/ja/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E4%BB%A3%E6%9B%BF%E5%80%A4

https://developer.mozilla.org/ja/docs/Web/CSS/var

> 関数の最初の引数は、置換されるカスタムプロパティの名前です。関数のオプションの 2 番目の引数は、代替値として機能します。最初の引数で参照されるカスタムプロパティが無効な場合、関数は2番目の値を使用します。
```
<var()> = 
  var( <custom-property-name> , <declaration-value>? )  
```


### `var()`の仕様

`var()` に未定義のCSS変数が記載されている場合 CSSプロパティは `none` を返す

```:CSSプロパティが無効となる例
filter: var(--undefined-vars) drop-shadow(0 0 35px rgba(0, 0, 255, 0.5))
```

https://drafts.csswg.org/css-variables/#guaranteed-invalid


ただし `,` 単体でフォールバックしている場合 `var()` 自体は 空の値 を返す

このときCSSプロパティの値は `none` にはならない

※ そのCSSプロパティの構文・書式を満たしていることが前提

```:CSSプロパティが有効となる例
filter: var(--undefined-vars,) drop-shadow(0 0 35px rgba(0, 0, 255, 0.5))
```

https://drafts.csswg.org/css-variables/#using-variables

### `drop-shadow-xxx` クラスによって生成されるCSS

TailwindCSS V4 で 出力されるCSSは以下の通り
```css
.drop-shadow-\[0_0_5px_rgba\(0\,0\,255\,0\.5\)\] {
    --tw-drop-shadow: drop-shadow(0 0 35px rgba(0, 0, 255, 0.5));
    
    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,)
        var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,)
        var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
```

上の例ではCSS変数の後ろに `,` を置きフォールバックを設定することで `var()` および `filter` の無効化を回避している

:::note
TailwindCSS V4 では `filter` を扱う際 沢山のCSS変数を用いて制御している

`drop-shadow-xxx` クラスを使う場合 `--un-drop-shadow` 以外のCSS変数は未定義であることがほとんどである

よってシャドーを描画させるためにはフォールバックが必要不可欠となる
:::

対して UnoCSS で出力されるCSSは以下の通り
```css
.drop-shadow-\[0_0_5px_rgba\(0\,0\,255\,0\.5\)\] {
    --un-drop-shadow: drop-shadow(0 0 35px rgba(0, 0, 255, 0.5));
    
    filter: var(--un-blur) var(--un-brightness) var(--un-contrast)
        var(--un-grayscale) var(--un-hue-rotate) var(--un-invert)
        var(--un-saturate) var(--un-sepia) var(--un-drop-shadow);
}
```

「未定義のCSS変数」に対して `,` によるフォールバックが設定されていない

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/a00a49fc-4ec9-406b-b66d-13b9e9ece396.png)|
<font color="gray" size="sm">左側では無効な `var()` により `filter` が `none` になっている</font>

これにより `var()` および `filter` プロパティが無効となり シャドーが描画されなくなる

### Chromeの仕様

Chrome DevTools (134.0.6998.178) は
`var()` の第二引数が空値（`,` のみ）であるとき <b>`var()` 内の `,` を省略して表示する</b>

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/05148d64-aac9-4f27-9c94-31c1f77b7229.png)|
<font color="gray" size="sm">[Tailwind CSS の PlayGround](https://play.tailwindcss.com/YXp8T1HlyZ) で `drop-shadow` クラスを利用している場面</font>

Chromeでは `drop-shadow` クラスのスタイルは上記のように表示される

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/36017e32-862a-4815-8374-f829625132b3.png)|
<font color="gray" size="sm">スタイルを選択範囲している場面</font>

この状態でクリップボードにコピーすると見た目通りの値がコピーされる

ここまでは極めて普通のことである

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/3a1a5055-ff46-4c29-a0e5-b3ea603f66d2.png)|
<font color="gray" size="sm">`filter` プロパティを編集するために値をクリックした場面</font>

しかしプロパティの編集モードに切り替えることで 省略されていた第二引数が表示された

この状態になってはじめて `,` を含めてコピーすることができる

（このChromeの仕様がフォールバック実装漏れの一因になったと推察できる）

:::note
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/bac33348-f254-47e8-b7cb-7139f2ae51af.png)

Firefox (136.0.3) では第二引数は省略されない
:::


## バグの修正

本事象を受けてUnoCSSにissueおよびPull Request（以下PR）を送付した

https://github.com/unocss/unocss

### コントリビュートガイドの確認

まずは以下2点を確認する

本リポジトリではまずissueを作成すること、その後PR内にissueのリンクを貼ることが通例となっている

https://github.com/unocss/unocss/blob/main/.github/CONTRIBUTING.md

またUnoCSSは [Anthony Fu](https://github.com/antfu) さんのリポジトリであるため以下内容も確認する

特に環境構築時やPR作成時に役立つ

https://github.com/antfu/contribute

### issueの作成

[事象/原因/詳細/対処]の見出しを立てて作成した

https://github.com/unocss/unocss/issues/4530

:::note
バグレポート用のissueの補足
- Reproductionという必須項目が存在する
  ここにはバグを再現した [PlayGround](https://unocss.dev/play/#version=) のURLを添付する
  - ライトモード・ダークモードそれぞれでテキストが背景色と同化しないようにすると見やすい
  - PlayGround のバージョンを指定するにはURL末尾に `#version=66.1.0-beta.6` のように入力してリロードする
- システム情報は任意項目
  - ただしCSSはブラウザや端末によって挙動や見え方が変化しうるためなるべく記載する
:::

### 環境構築

基本ガイドラインを頼りに進めていく

今回はnode:23イメージからDockerコンテナ環境を作成した

一通り準備が完了したらvitestで今回の改修箇所のテストが通るか確認する


### バグ修正

CSS変数名からあたりを付けて修正→ビルド→playgroundで動作確認 を繰り返す

最後にテストファイルも修正してvitestが通ることを確認する


### PR作成

https://github.com/unocss/unocss/pull/4527

CIが途中で止まるが以下の実行には権限ユーザの承認が必要となる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/0d02c76f-4d2f-4724-8075-296b0914b73b.png)

ここはただ待つのみである
※ 今回は2～3日後に承認いただいた

その後CIに成功してレビュワーによってマージされれば無事終了となる


## まとめ

メンテナーの方にもご協力いただいたことで[66.1.0-beta.7](https://github.com/unocss/unocss/releases/tag/v66.1.0-beta.7)以降のUnoCSSでは `drop-shadow` クラスを正常に使えるようになった

Chromeではスタイル表示時に空値フォールバックを隠してしまうため、 `var()` の実値を確認する際には一度プロパティ値をクリックすること