お疲れ。今日も今日とてCSSの沼にハマっているかい?
フロントエンドをやっていると、「ボタンをグレーアウトして、ユーザーに押せないことを伝える」なんて要件は、朝飯前レベルで毎日書くはずだ。
で、だ。大半のジュニアや中級初期のエンジニアは、こういう時になんの疑いもなくこう書く。
/ よく見かける「とりあえず動く」コード /
.my-button.is-disabled {
background-color: #ccc;
color: #666;
cursor: not-allowed;
}
おいおい、ちょっと待て。クラス名をわざわざJSで付け外ししたり、HTML側で `class=”btn is-disabled”` なんて冗長なこと、まだやってないだろうな?
HTMLの仕様書には、誇らしげに `disabled` 属性が用意されている。そしてCSSには、その属性を持つ要素をスマートに捉えるための強力な疑似クラス、`:disabled` があるんだ。
今回は、この `:disabled` 疑似クラスについて、ブラウザの裏側の動きから、実務で絶対に知っておくべき「一歩先のハック」まで、シニアの俺がみっちり叩き込んでやろう。心して聞いてくれ。
—
1. なぜクラスではなく `:disabled` 疑似クラスを使うべきなのか?
まず大前提として、HTMLの `
これをJavaScriptやCSSの独自クラスで制御しようとすると、何が起きるか?
- アクセシビリティ(a11y)の崩壊: スクリーンリーダーは、単に色が変わっただけの `.is-disabled` クラスを「無効な要素」として認識してくれない。ネイティブの `disabled` 属性があって初めて、支援技術は `aria-disabled=”true”` と同等のコンテキストを読み取れる。
- 状態の二重管理: HTMLの属性と、CSS(またはJS)のクラス名の状態が同期ズレを起こし、バグの温床になる。
つまり、「HTMLの属性変化を、CSSの疑似クラスで直に捉える」 のが、モダンなフロントエンドにおける絶対的な正義だ。ブラウザのネイティブ機能に全投げできるものは、極力そうするべきなんだよ。
—
2. ブラウザの裏側で何が起きているのか?(詳細度とスタイリングの真実)
CSSを書く上で、ブラウザのレンダリングエンジンがどう解釈しているかを知るのはエンジニアの嗜みだ。
`:disabled` 疑似クラスが適用された要素は、ブラウザの内部的な状態フラグが変化する。ここで一つ、実務で絶対にハマる「詳細度(Specificity)」の罠について話しておこう。
実は、`:disabled` 自体の詳細度は、一般的なクラスセレクタ(`.class`)と同じ、`(0, 1, 0)` として計算される。
しかし、ここで問題になるのが 「ユーザーエージェントスタイルシート(ブラウザ標準スタイル)」との戦い だ。
例えば、多くのブラウザでは `
/ ブラウザのデフォルト(イメージ) /
button:disabled {
opacity: 0.5;
cursor: default;
}
もし君が、自分で書いたカスタムボタンのCSSでこう書いたとする。
.my-awesome-button {
background-color: #0066cc;
cursor: pointer;
}
/ 詳細度が負ける例 /
:disabled {
background-color: #e0e0e0;
}
これだと、詳細度の勝負には勝てても、プロパティの継承や他のコンポーネントとの詳細度のバッティングでスタイリングが意図通りに当たらないことが多々ある。
実務では、必ず対象のセレクタと組み合わせて、詳細度を意図的にコントロールする必要があるんだ。
/ 模範解答:セレクタを連結して詳細度を担保する /
button.my-awesome-button:disabled {
background-color: #e0e0e0;
color: #888888;
cursor: not-allowed;
box-shadow: none; / ホバー時の影などをリセットするのを忘れずに! /
}
—
3. 現場で即戦力になる!コピペ可能な実践的スタイリングパターン
理屈はこれくらいにして、明日からそのままプロダクトに組み込める「実用的なCSSスニペット」を授けよう。
ただグレーアウトするだけじゃ、UI/UXデザインとしては三流だ。「押せなそう」な見た目と同時に、「ホバーしても何も起きない(イベントが発火しない)安心感」 をCSSで担保するのがプロの仕事だ。
/ ==========================================
実務で使える堅牢なボタンのdisabledスタイル
========================================== /
.c-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
font-weight: 600;
border-radius: 4px;
border: 1px solid transparent;
background-color: #2563eb; / 通常時のプライマリカラー /
color: #ffffff;
cursor: pointer;
transition: background-color 0.2s ease, opacity 0.2s ease;
}
/ 通常時のホバー・アクティブ状態 /
.c-button:hover:not(:disabled) {
background-color: #1d4ed8;
}
.c-button:active:not(:disabled) {
background-color: #1e40af;
}
/
★ここが本丸::disabled 状態のスタイリング
/
.c-button:disabled {
/ 1. 背景と文字色をグレーアウト /
background-color: #e5e7eb;
color: #9ca3af;
/ 2. ボーダーがある場合は薄くするか透明にする /
border-color: #d1d5db;
/ 3. カーソルを「禁止マーク」に変更 /
cursor: not-allowed;
/ 4. 念のため、ホバーやフォーカス時のエフェクトを完全に打ち消す /
box-shadow: none;
transform: none;
/ 5. 視覚的な軽さを出すために、わずかに透過させるのも手 /
opacity: 0.8;
}
チーフアーキテクトからのワンポイントアドバイス
ここで注目してほしいのが、`.c-button:hover:not(:disabled)` という書き方だ。
「disabledになっていない時だけホバーを効かせる」というこの書き方をしておかないと、不具合でdisabledになっているはずのボタンにマウスを乗せた時、なぜかホバーカラーに変わってしまう という悲惨なバグを生む。
これを防ぐための `:not(:disabled)` の合わせ技は、実務では常識レベルのテクニックだから必ず覚えておいてくれ。
—
4. ちょっとマニアックな応用:カスタムフォーム要素へのアプローチ
標準の `` や `` は、OSやブラウザによって見た目がバラバラだ。そのため、モダンなWEBアプリでは `appearance: none;` を使って完全にカスタムデザインすることが多いはずだ。
その時、チェックボックスが無効化された(`disabled`)状態をどうCSSで拾うか?
HTML構造がこうなっているとしよう。
この場合、インプット要素自体は隠されている(あるいはカスタムパーツの下にいる)ため、隣接セレクタ(`+`)や兄弟結合子(`~`) を使って、後ろに続くカスタムパーツの見た目を制御する必要がある。
/ チェックボックスのカスタムスタイル /
.c-checkbox {
display: inline-flex;
align-items: center;
cursor: pointer;
position: relative;
}
/ ネイティブのチェックボックスは視覚的に隠す /
.c-checkbox input[type=”checkbox”] {
position: absolute;
opacity: 0;
cursor: pointer;
}
/ カスタムしたチェックマークの見た目 /
.c-checkbox__mark {
width: 1.25rem;
height: 1.25rem;
border: 2px solid #d1d5db;
border-radius: 4px;
background-color: #ffffff;
margin-right: 0.5rem;
}
/
★裏技:inputがdisabledのとき、兄弟要素の.c-checkbox__markをスタイリングする
/
.c-checkbox input[type=”checkbox”]:disabled ~ .c-checkbox__mark {
background-color: #f3f4f6;
border-color: #e5e7eb;
cursor: not-allowed;
}
/ ラベル全体の文字色も薄くする /
.c-checkbox input[type=”checkbox”]:disabled ~ span:last-of-type {
color: #9ca3af;
cursor: not-allowed;
}
このように、`:disabled` は自分自身だけでなく、周辺のUIパーツの状態連動においても強力なフックとして機能する。
—
まとめ
`:disabled` 疑似クラスは、単なる「見た目を灰色にする機能」じゃない。
HTMLのアクセシビリティを担保し、JSの余計なクラス制御のボイラープレートを削ぎ落とし、CSSだけで堅牢なUI状態を管理するための「フロントエンドエンジニアの強力な武器」だ。
今日から自社のコードベースを見返してみて、「不要な `.is-disabled` クラス」が蔓延していたら、ぜひ今回の知見を使ってシュッとリファクタリングしてみてくれ。コードが美しくなるだけでなく、アクセシビリティのスコアも確実に跳ね上がるはずだ。
それじゃ、また次のコードレビュー会で会おう。いいコードを書くんだぞ!

コメント