社内技術勉強会で「良いHTML、CSSの書き方」について話しました

RightTouchでデザインエンジニアをしている @kan_dai です。
RightTouchでは定期的に技術勉強会を開催していて、その中で「良いHTML、CSSの書き方」という内容で発表したのでブログで紹介してみます。

良いHTMLとは

一般的にはセマンティックHTMLと呼ばれる、Webページの構造やコンテンツを意味的に正しく表現しているHTMLが良いとされています。
具体的には、見出しには h1 タグ、段落には p タグを使うなど正しいHTMLタグを使ってマークアップを行うことで実現できます。

良いHTMLを書くとどうなるのか

良いHTMLを書くことで以下のようなことが期待できます。

  • マシンリーダブルになる
    • 検索エンジンが理解しやすくなることでSEOで優位になる
  • ユーザビリティやアクセシビリティが高まる
    • とくにスクリーンリーダーや音声ブラウザでアクセスしやすくなることでアクセシビリティの向上が期待できる
  • 人間にとってもコードの可読性が高まり、メンテナンス性が向上する

良いことばかりですね!

これだけのメリットがあるので、常に最適なマークアップを心がけたいですが、良いHTMLを書くのにはそれなりに知識や労力が必要です。

実際の現場では、求められている基準やかけられる工数によってどこまでやるかが変わってくると思います。

良いHTMLの重要度

求められる基準と書きましたが、閲覧する人や環境によって良いHTMLの重要度は変わり、これらが限定できる場合は重要度は相対的に下がると考えています。

たとえば、RightTouchで提供しているRightSupportというサービスの例で説明します。

重要度(低):管理画面

dashboard.png

管理画面を閲覧するのは、RightSupportを契約した企業の方だけで、どういった方がどういう使い方をするかというのがある程度限定できる上、何かあればサポートの体制もあります。

また、検索エンジンから訪れることもないためSEOを気にする必要もありません。

もちろん品質が低くて良いという話ではないですが、良いHTMLの重要度という意味では、他の機能と比べると相対的に低くなります。

重要度(中):ウィジェット

widget.png

RightSupportでは、指定したWebページにユーザーをサポートするウィジェットを表示できます。ウィジェットは補助的なものであり、基本的には設置されるサイトのメインコンテンツがあるため、こちらもSEOを気にする必要はありません。

ただ、どんなユーザーが訪れるかわからないため、操作性やアクセシビリティに関しては管理画面に比べると重要度は高くなります。

重要度(高):サポートサイト

supportsite.png

RightSupportでは管理しているFAQを利用してサポートサイトを作成する機能があり、RightSupportのサポートサイト もこの機能で構築されています。

サポートサイトはさまざまなユーザーが利用することが想定され、高いアクセシビリティが求められます。また、サイト全体を構築する機能で、検索エンジンからの流入も重要であるため、SEOにも気をつける必要があります。

このような特徴があるため、サポートサイトは良いHTMLの重要度が高いと考えています。

インタラクティブ・コンテンツを使う場合は注意する

重要度の話をしましたが、インタラクティブ・コンテンツに関しては重要度に関係なく気をつけたいというのがあります。

インタラクティブ・コンテンツとは、ユーザーが操作可能なコンテンツのことで、HTMLタグでは、たとえば a button input などです。

インタラクティブ・コンテンツはそれぞれのタグによって振る舞いがあり、タグの使い方が使い勝手に大きく関わってきます。

たとえば、a タグはリンクですが、押した時にリンク先に遷移するだけであれば div タグでも、クリック時に JavaScript で実行可能です。ただ、div タグだとフォーカスされないのでキーボード操作が難しいなど、細かい振る舞いの違いが出てきます。

フォーカスしないだけであれば、tabindex を使用したり button タグで代用するなどが可能です。
しかし、 a タグにはこれ以外にも以下のような特徴があります。

  • ホバー時に左下にリンク先のURLが表示される
  • 特殊ボタン(altやcontrol)を押しながら操作すると別タブで開くなど特殊な操作になる
  • コンテキストメニューの内容が変わる
  • スクリーンリーダーの読み上げが変わる

これらのことをすべて独自でやろうとすると非常に大変、またはできない可能性もあると思いますが、正しいタグを使うだけで解決できます。

そもそもこういったタグの振る舞いをすべて把握することも大変なので、自分の気づかないところをカバーしてもらうという意味でも、インタラクティブ・コンテンツに正しいタグを使うことはユーザーの操作性を担保するという意味で気をつけたいところです。

WAI-ARIAについて

WAI-ARIAの使い所がわからないという意見があったのでそれについても話しました。

ここは僕自身もすごく詳しいわけではないのでもう少し勉強したいところですが、必要な時以外は使わなくて良い と考えています。

一点忘れてはいけないのが、 WAI-ARIA は必要な場合のみ使用するという点です。

これはMDNにも同様のことが書いてありました。

WAI-ARIAには、ロール プロパティ ステート の機能があります。

まず、ロールに関しては正しいHTMLタグを使うことを考える方が重要で、既存のHTMLタグで表せない意味づけが必要な場合は使用しますが、ケースとしてはそんなに多くないと考えています。

プロパティとステートもHTMLの属性で表現できない場合は使用するという感じで、たとえば以下のような場合はHTMLの属性で表現できるので、この例の aria属性 は過剰だと考えています。

<button disabled aria-disabled="true">ボタン</button>

以下のような現状のHTML属性では表現できないものは、使うと良さそうな例として挙げました。

  • アコーディオンに対する aria-expandedaria-controls
  • タブに対する aria-selectedaria-hidden
  • アイコン要素の aria-hidden

ただ、これらも今後新しいHTMLタグで表現できるようになる可能性はあると思っています。

また、個人的には WAI-ARIA を正しく使用するのは難しいと思っているので、最近では headless UIRadix UI のようなライブラリを使用するのを勧めています。

良いCSSとは

僕が良いCSSで思い浮かべるのは2012年に書かれた CSS Architecture という記事です。

記事内で、優れたCSS設計は 予測しやすい 再利用しやすい 保守しやすい 拡張しやすい という4つの特徴があると書かれています。

この考え方は多くの開発者の共通認識となり、BEMFLOCSS など命名規則を用いてCSSをコントロールする設計手法が多く生み出されました。

これらの設計手法はWebサイト制作においては今も現役ですが、Webアプリケーション開発においては、CSS in JSやJSフレームワークの機能を使ったScopedCSSや、TailwindCSSに代表されるUtility FirstCSSを使ってCSSを書くことの方が最近では主流になっています。(僕が観測している範囲の話です)

これらの手法が主流になっている理由として、近年主流のコンポーネントベースの開発では、影響範囲をコンポーネント内に絞れるという特徴があります。それによって、優れたCSS設計で挙げられていた、予測しやすく、保守や拡張もしやすいという部分が簡単に実現できるようになったためと考えています。
(再利用しやすいという部分は、CSS設計というよりはコンポーネントによって実現されていると考えます)

具体で普遍的に使えそうな6選

このように、時代によってCSSの書き方は変わっていくものという背景はありながらも、普遍的に使えそうな考え方やテクニックについて話したので紹介します。

新しい機能は積極的に使う

テクニックではなく考え方の話ですが個人的にはとても重要です。

新しい機能は、できなかったことができるようになる、今まで難しかったことが簡単にできるようになるなど今までの課題を解決してくれることが多いので、積極的に使用することでより良いCSSになると考えています。

ここ数年で使えるようになっている機能は便利なものが多く、最近のCSSの進化は個人的にはとても嬉しいです。

詳細な説明は割愛しますが、勉強会では例として以下のようなものを紹介しました。

HTMLタグにスタイルを当てない

HTMLタグに対してスタイルを指定した場合、タグの変更をした場合にCSS側の変更もする必要が出てしまい保守性が低くなります。

以下の例だと p タグを div に変更しようとするとテキスト部分に指定していたスタイルが当たらなくなってしまいます。

また、元々の div に指定していたスタイルが新しい div にも適用されるなど、予期せぬところに影響する可能性も出てきてしまいます。

<div>
    <p>Hello World</p>
</div>

<style>
    div {
        padding: 1rem;
    }

    p {
        font-size: 16px;
    }
</style>

そのため、基本的には以下のようにクラス名を指定してスタイルを当てるのが良いと思います。

<div class="hoge">
    <p class="fuga">Hello World</p>
</div>

<style>
    .hoge {
        padding: 1rem;
    }

    .fuga {
        font-size: 16px;
    }
</style>

汎用的なコンポーネントにマージンを使わない

コンポーネント自体にマージンを持たせると汎用的に使いにくくなってしまいます。

以下のように書くと、余白をもっと狭くしたいなどのケースで使いにくいです。

<button class="button">ボタン</button>
<button class="button">ボタン</button>

<style>
    .button {
        display: inline-block;
        margin: 0 16px;
    }
</style>

下記のように親コンポーネントなどレイアウト用の要素で制御してあげるのが良いです。

最近だと gap を使用するのが簡単だと思います。

<div class="flex">
    <button class="button">ボタン</button>
    <button class="button">ボタン</button>
</div>

<style>
    .flex {
        display: flex;
        gap: 16px;
    }
</style>

汎用的なコンポーネントに固定値を使わない

たとえば width: 1000px の要素を 800px の幅のコンテンツに配置するとはみ出してしまうように、汎用的なコンポーネントに固定の値を入れると変更に弱くなってしまいます。

<div class="box">Lorem ipsum</div>

<style>
    .box {
        width: 100px;
        height: 100px;
    }
</style>

代表的なプロパティだと widthheight になりますが、指定する場合は width: 100% のようにできるだけ相対値を指定すると変更に強くなります。

また、最低幅や高さを保ちたい場合は min-heightmax() 関数などを使用します。

<div class="box">Lorem ipsum</div>

<style>
    .box {
        width: 100%;
        min-height: 100px;
        height: max(100%, 100px);
    }
</style>

コンポーネントの装飾パターン

コンポーネントにバリエーションを持たせたい場合や状態によって見た目を変更したいケースで、以下のように書くと冗長になってしまいます。

<button class="button">ボタン</button>
<button class="button-primary">プライマリーボタン</button>
<button class="button-secondary">セカンダリーボタン</button>

<style>
    .button {
        display: inline-flex;
        padding: 1rem;
        border: 1px solid #eee;
    }

    .button-primary {
        display: inline-flex;
        padding: 1rem;
        border: 1px solid #eee;
        background-color: green;
    }

    .button-secondary {
        display: inline-flex;
        padding: 1rem;
        border: 1px solid #eee;
        background-color: blue;
    }
</style>

共通のクラスを用意して、マルチクラスや属性セレクターで追加のスタイルを指定すると簡潔に書くことができます。

<button class="button">ボタン</button>
<button class="button -primary">プライマリーボタン</button>
<button class="button" data-type="secondary">セカンダリーボタン</button>

<style>
    .button {
        display: inline-flex;
        padding: 1rem;
        border: 1px solid #eee;
    }

    .button.-primary {
        background-color: green;
    }

    .button[data-type='secondary'] {
        background-color: blue;
    }
</style>

余分なプロパティや上書きをしないように書く

以下のようなHTMLがあって、.listItem 間にマージンを持たせたい場合を考えてみます。

<ul>
    <li class="listItem">アイテム1</li>
    <li class="listItem">アイテム2</li>
    <li class="listItem">アイテム3</li>
</ul>

以下だと、実現はできますが打ち消し用の指定も必要になるので冗長になってしまいます。

.listItem {
    margin-top: 16px;
}

/* 打ち消し用の指定が必要 */
.listItem:first-child {
    margin-top: 0;
}

:not() 擬似クラスや隣接セレクターを活用することで簡潔に書くことができます。

/* :not() 擬似クラスを使った例 */
.listItem:not(:first-child) {
    margin-top: 16px;
}

/* 隣接セレクターを使った例 */
.listItem + .listItem {
    margin-top: 16px;
}

また、レスポンシブで実装する場合はモバイルファーストで書くのを推奨します。

以下は簡単な例で、画面サイズが大きい場合は3カラム、小さい場合は1カラムのグリッドレイアウトを作るコードです。

1カラムの場合は grid-template-columns の指定をしなくても良いですが、PCファーストで書いた場合はすでに3カラム用の指定があるため、打ち消し用の記述が必要になります。

/* PCファースト */
.grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 767px) {
    .grid {
        grid-template-columns: 1fr;
    }
}

/* モバイルファースト */
.grid {
    display: grid;
    gap: 1rem;
}

@media (min-width: 768px) {
    .grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

自分の経験上、画面サイズが大きいほどCSSが複雑になる傾向があり、最初に大きい画面用のCSSを書くとモバイルファーストの場合は必要なかった打ち消し用のCSSを書くことが増えてしまいます。

最期に

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

技術勉強会の中で紹介した良いHTMLと良いCSSの書き方について紹介しました。
今後の技術の進化の中で、HTMLやCSSの書き方が変わっていく可能性は高いと思いますが、現時点で良いと思っている書き方を紹介したので少しでも参考になれば嬉しいです。

今後もRightTouchの中で使用している技術や勉強会の内容について紹介していければと思います。