はじめに
最近 Tailwind を使って実装することがあり、HTML の class 属性で見た目の定義をすることに何となく違和感を感じていました。それをきっかけに、HTML と CSS の役割分担について考察してみたので、ここではその内容を記載してみます。
尚、Tailwind を批判する目的はありません。自分の今までの取り組み方の問題点に気づくまでの過程を説明するのに都合が良かったので Tailwind を使用しています。
TL;DR
Tailwind の良いと思うところ
-
CSSファイルが不要
HTMLファイルとCSSファイルを行ったり来たりする手間を省けます。 -
スタイルの適用範囲が明確
class属性の要素に対してのみスタイルが適用されるので、他の要素への影響を気にせずに実装できます。 -
CSS の長い記述を書かなくて良い
全体のコード量を減らすことができます。 -
意識せずにCSSのベストプラクティスを享受できる
UIデザインのプロフェッショナル達の多くの経験を踏まえた知識が反映されています。
例:
<!-- HTML & CSSファイル-->
<button type="button">キャンセル</button>
[cssファイル]
button {
width: 12px;
border-radius:0.25rem;
background-color: rgb(226 232 240);
border-color: rgb(75 85 99);
}
<!-- Tailwind -->
<button class="w-56 rounded bg-slate-200 border-gray-600">キャンセル</button>
Tailwind の良くないと思うところ
-
HTML が見づらくなる
HTMLの構造が複雑になると長いクラス名がプログラマーにとって邪魔になってきます。これによって勘違い等、判断ミスが起きやすくなると思います。
ただし、@apply を使うことで改善できそうです。 -
細かな点を調整しづらい
Tailwind で用意されていないスタイルを適用するには別途調整が必要です。例えばborder-color
で半透明を使う場合等です。 -
参照すべきドキュメントが増える
CSS の知識を完全に不要にしてくれるわけではないので、参照すべきドキュメントが増えます。
気づいたこと
Tailwind に触れたことで、気づいたことがあります。今までHTMLをコーディングする際に class 属性に 構造上の意味
と 見た目上の意味
を混在させていたのです。
例えば以下のコードの場合、
<button type="button" class="btn-cancel btn-basic w-56 rounded">キャンセル<button>
このように分類できます。
構造上の意味 | 見た目上の意味 |
---|---|
btn-cancel | btn-basic w-56 rounded |
class 属性には 構造上の意味
のみを記載したほうが、全体の見通しが良くなるのでは?と感じました。そして、class 属性のドキュメント を確認してみると、同じようなことが記載されていたのでちょっと自信を持てました
仕様書ではクラス名の要件を示していませんが、ウェブ開発者は要素の外見ではなく、意味論的な目的を表す名前を使用することが推奨されます。
改善策を考える
以降、Tailwind を離れて、私なりのこうしたら良いのでは、という改善策を考えてみます。
改善その1:CSS のカスタムプロパティ―を使ってCSSの値を共通化
CSS にはカスタムプロパティ という機能があり、これによって CSS の値を共通化できます。
class 属性には 構造上の意味
を指定する前提で例を提示します。
<button type="button" class="btn-cancel">キャンセル</button>
<button type="submit" class="btn-register">登録</button>
.btn-cancel {
width: 12px;
border-radius:0.25rem;
background-color: gray;
border-color: black;
}
.btn-register {
width: 12px;
border-radius:0.25rem;
background-color: blue;
border-color: #0ff;
}
+:root {
+ --btn-basic-width: 12px;
+ --btn-basic-border-radius: 0.25;
+ --basic-bg-color: gray;
+ --basic-border-color: black;
+ --primary-border-color: #0ff;
+ --primary-bg-color: blue;
+}
.btn-cancel {
- width: 12px;
- border-radius:0.25rem;
- background-color: gray;
- border-color: black;
+ width: var(--btn-basic-width);
+ border-radius: var(--btn-basic-border-radius);
+ background-color: var(--basic-bg-color);
+ border-color: var(--basic-border-color);
}
.btn-register {
- width: 12px;
- border-radius:0.25rem;
- background-color: blue;
- border-color: #0ff;
+ width: var(--btn-basic-width);
+ border-radius: var(--btn-basic-border-radius);
+ background-color: var(--primary-bg-color);
+ border-color: var(--primary-border-color);
}
構造上の意味 | 見た目上の意味 |
---|---|
btn-cancel | --btn-basic-width --btn-basic-border-radius --basic-bg-color --basic-border-color |
btn-regisrer | --btn-basic-width --btn-basic-border-radius --primary-bg-color --primary-border-color |
う~ん、見た目上の意味
の粒度が細かすぎますね。
できればもっとシンプルに定義したいところです。
改善その2:SASS の @mixin
@include
を使ってCSS宣言ブロックを共通化
SASS の @mixin
と @include
を使うことで粒度を大きくできます。
私は SASS に詳しくないので、以下のコードを実現できるかどうかは分かってません。雰囲気だけで書いてます!
<button type="button" class="btn-cancel">キャンセル</button>
<button type="submit" class="btn-register">登録</button>
:root {
--btn-basic-width: 12px;
--btn-basic-border-radius: 0.25;
--basic-bg-color: gray;
--basic-border-color: black;
--primary-bg-color: blue;
--primary-border-color: #0ff;
}
@mixin btn-basic {
width: var(--btn-basic-width);
border-radius: var(--btn-basic-border-radius);
background-color: var(--basic-bg-color);
border-color: var(--basic-border-color);
}
@mixin btn-primary {
width: var(--btn-basic-width);
border-radius: var(--btn-basic-border-radius);
background-color: var(--primary-bg-color);
border-color: var(--primary-border-color);
}
.btn-cancel {
@include btn-basic
}
.btn-register {
@include btn-primary
}
構造上の意味 | 見た目上の意味 |
---|---|
btn-cancel | btn-basic |
btn-regiser | btn-primary |
良い感じです!
しかも、構造上の意味
から 見た目上の意味
に変換するマッピング定義が近い場所でかつシンプルに実現できています。
.btn-cancel {
@include btn-basic
}
.btn-register {
@include btn-primary
}
また、見た目上の意味
の粒度が階層化されている点もいい感じです。
粒度大 | 粒度小 |
---|---|
btn-basic | --btn-basic-width --btn-basic-border-radius --basic-bg-color --basic-border-color |
btn-primary | --btn-basic-width --btn-basic-border-radius --primary-bg-color --primary-border-color |
ただし、HTML と CSS が別のファイルで管理されているので、行ったり来たりしなければなりませんし、不用意に core.scss を修正してしまうと他のHTMLファイルに思いもよらない影響を及ぼしてしまうリスクがあります。
改善その3:Scoped CSS で HTML と CSS を同じファイルで実装
Svelte や Vue などで採用されている Scoped CSS を利用することで、さらに改善することができます。
Scoped CSS とはコンポーネント内でのみ適用される CSS のことです。※注:Scoped CSS
という正式な用語があるわけではありません。
Svelte の例 です。
<script>
import Message from './Message.svelte';
</script>
<p>Hello</p>
<Message />
<style>
p {
font-weight: bold;
color: red;
}
</style>
<p>こんにちは!</p>
結果はこうなります。
Hello
にはスタイルが適用されてますが こんにちは!
には適用されていません。つまり、App.svelte コンポーネントのみにスタイルが適用されていることが分かります。これが Scoped CSS です。
というわけで、こんな感じになります。
Svelte で SCSS を定義できるかどうか分かってません。雰囲気だけで書いてます!
[追記:2024/6/26]
設定を変えるだけで実現できることを確認できました!
Svelte Lab で確認できます。
<button type="button" class="btn-cancel">キャンセル</button>
<button type="submit" class="btn-register">登録</button>
<style lang="scss">
.btn-cancel {
@include btn-basic
}
.btn-register {
@include btn-primary
}
</style>
:root {
--btn-basic-width: 12px;
--btn-basic-border-radius: 0.25;
--basic-bg-color: gray;
--basic-border-color: black;
--primary-bg-color: blue;
--primary-border-color: #0ff;
}
@mixin btn-basic {
width: var(--btn-basic-width);
border-radius: var(--btn-basic-border-radius);
background-color: var(--basic-bg-color);
border-color: var(--basic-border-color);
}
@mixin btn-primary {
width: var(--btn-basic-width);
border-radius: var(--btn-basic-border-radius);
background-color: var(--primary-bg-color);
border-color: var(--primary-border-color);
}
構造上の意味
と 見た目上の意味
のマッピングを最小限のスコープ(Sample.svelte ファイル内)で定義することができました。これで、他のファイルに影響を与えずにマッピングを自由に変更できますね!
構造上の意味
と 見た目上の意味
をどうやって判別するのか?
Svelte Society が毎週 YouTube にアップしている This Week in Svelte という動画がヒントになりました。
この動画では、Svelte に関連する話題を提供したり、議論したりしています。その中で度々出てくるのがアクセシビリティに関する議論です。あらゆる Web サービスはどんなハンディキャップを持たれている人に対してもアクセスできるべきだよね、といった考え方を基準にして議論されています。主に視覚的ハンティキャップを持たれている人を想定していることが多いようです。スクリーンリーダーを起動して Web サイトにアクセスした場合にどうなるのか、実演してくれています。Svelte 以外でもとても勉強になる動画です。
ヒントになったのは、HTML は視覚的ハンディキャップを持たれている人にとって意味のある構造であるべき、という意見です(たぶんそんな感じだったと思います)。視覚的ハンディキャップを持たれている人にとって Web サイトの見た目がどうなっているかは認識できません(もしくは認識しづらい)。つまり、CSS は意味をなさないのです。HTML の構造だけで判断するしかないのです。
よって、私が思いついた判別方法は以下になります。
視覚的ハンディキャップを持たれている人にとって有益な情報は 構造上の意味
とする
今後の課題
ここで記載した改善策を実現できるのかどうか、効果があるのかどうか、実践してみる必要がありますね。私にとっては結構ボリュームがあるので少しづつ検証してみます。
まとめ
Tailwind をきっかけにして、今までの自分の実装では HTML の class 属性に 見た目上の意味
が入り込みすぎてたことに気づきました。HTML ではできるだけ 構造上の意味
のみを表現するように実装するのがよさそうだと感じました。それを実現するために思い付きで改善策を考えてみましたが、実現可能かどうかに関わらず、自分自身が勉強になったのでとりあえず良しとします!