「外部サイトへのリンクにだけアイコンを付けたい」「PDF へのリンクだけ色を変えたい」——こんなとき、わざわざクラス名を付けて回らなくても、CSS の属性セレクタを使えば HTML を書き換えずにスタイルを当てられます。href や type といった属性の値を手がかりに要素を選ぶ書き方で、前方一致・後方一致・部分一致まで指定できます。この記事では7種類ある書き方をひととおり整理し、実際のフォームやリンクでの使いどころ、そして効かないときの原因までを解説します。
目次
属性セレクタは「属性の有無や値」で要素を選ぶ
普段よく使う .button のようなクラスセレクタは、あらかじめ HTML にクラスを書いておく必要があります。それに対して属性セレクタは、要素がもともと持っている属性を条件にして選びます。角括弧の中に属性名を書くのが基本の形です。
/* title 属性を持っている要素すべて(値は問わない) */
[title] {
border-bottom: 1px dotted #999;
}
/* target 属性の値がちょうど _blank の a 要素 */
a[target="_blank"] {
color: #007bff;
}
角括弧の前に要素名を書くと「その要素のうち条件に合うもの」に絞られ、省略すればすべての要素が対象になります。属性名の大文字・小文字は HTML では区別されないので、[TITLE] と書いても同じように動きます。
7種類の書き方と一致のしかた
属性セレクタには、値の照合のしかたが異なる7つの形があります。よく使うのは = による完全一致と、^= $= *= の部分照合の4つです。
| 書き方 | 一致する条件 | 例 |
|---|---|---|
[attr] | その属性を持っている(値は問わない) | [disabled] → 無効化された部品 |
[attr="値"] | 値がちょうど一致する | [type="text"] |
[attr~="値"] | 空白区切りの単語のどれかと一致する | [class~="btn"] |
[attr|="値"] | 値そのもの、または「値-」で始まる | [lang|="en"] → en, en-US |
[attr^="値"] | その文字列で始まる(前方一致) | [href^="https://"] |
[attr$="値"] | その文字列で終わる(後方一致) | [href$=".pdf"] |
[attr*="値"] | その文字列を含む(部分一致) | [href*="example"] |
~= と |= は少し分かりにくいので補足します。~= は、class="card btn large" のように空白で区切られた複数の単語を持つ属性のために用意された形で、そのうちの1つと一致すればマッチします。|= は言語コードのために作られた形で、[lang|="en"] は lang="en" と lang="en-US" の両方にマッチしますが、lang="english" にはマッチしません。ハイフンで区切られた前半部分だけを見る、という動きです。
なお ^= $= *= の3つで、値に空文字([href^=""])を指定すると何にもマッチしません。JavaScript で値を組み立ててセレクタを作るときは、空になるケースに注意してください。
リンク先の種類に応じてアイコンを付ける
属性セレクタが最も活きるのが、href の値でリンクを分類する場面です。外部サイトへのリンク、PDF へのリンク、メールリンクは href の書き出しや末尾がそれぞれ違うので、クラスを付けなくても見分けられます。::before や ::after と組み合わせて目印を足してみましょう。
<ul class="links">
<li><a href="https://developer.mozilla.org/ja/">MDN Web Docs</a></li>
<li><a href="/about/">運営者について</a></li>
<li><a href="/files/manual.pdf">利用マニュアル</a></li>
<li><a href="mailto:info@example.com">メールで問い合わせる</a></li>
</ul>
.links {
font-family: sans-serif;
line-height: 2;
}
/* https:// で始まる = 外部サイトへのリンク */
a[href^="https://"]::after {
content: "↗";
margin-left: 4px;
color: #007bff;
}
/* .pdf で終わる = PDF ファイルへのリンク */
a[href$=".pdf"]::before {
content: "PDF";
margin-right: 6px;
padding: 2px 6px;
border-radius: 3px;
background: #e53935;
color: #fff;
font-size: 11px;
}
/* mailto: で始まる = メールリンク */
a[href^="mailto:"]::before {
content: "✉";
margin-right: 4px;
}
ポイントは [href^="https://"] の書き方です。サイト内リンクを /about/ のようなルート相対パスで書いていれば、https:// で始まるのは外部サイトだけになるため、これだけで外部リンクを判別できます。自サイトの URL を絶対パスで書いている場合は、自サイト分を除外する必要があります。:not() と組み合わせると次のように書けます。
/* https:// で始まり、かつ自サイトのドメインではないリンク=外部リンク */
a[href^="https://"]:not([href*="webool.net"])::after {
content: "↗";
margin-left: 4px;
}
ファイル形式で分けたいときは後方一致の $= が便利です。[href$=".pdf"] や [href$=".zip"] のようにセレクタを並べておけば、リンクを追加するたびにクラスを書く必要がなくなります。
フォーム部品を type や状態で狙い撃ちする
input 要素は、type の値によって見た目も役割もまったく違うのに、要素名はどれも input です。そのため input { } とだけ書くと、テキスト入力欄にもチェックボックスにも送信ボタンにも同じスタイルが当たってしまいます。ここで属性セレクタを使って type ごとに分けるのが定番の使い方です。
<form class="form-demo">
<input type="text" value="テキスト入力">
<input type="email" value="mail@example.com">
<input type="text" value="入力必須の項目" required>
<input type="text" value="編集できません" disabled>
<input type="submit" value="送信する">
</form>
.form-demo {
display: grid;
gap: 12px;
max-width: 320px;
font-family: sans-serif;
}
/* type の値ごとにスタイルを分ける */
input[type="text"],
input[type="email"] {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
/* required 属性が付いている入力欄だけ左に赤い線 */
input[required] {
border-left: 4px solid #e53935;
}
/* disabled 属性が付いている入力欄はグレーに */
input[disabled] {
background: #eee;
color: #999;
}
input[type="submit"] {
padding: 10px;
border: none;
border-radius: 4px;
background: #007bff;
color: #fff;
font-size: 15px;
}
デモの [required] と [disabled] のように、値を持たない属性も属性セレクタで選べます。これらは HTML では属性名を書くだけで有効になる真偽値属性なので、[required] と属性名だけを書けば「その属性が付いている要素」にマッチします。
ただし、状態を表すものには :disabled や :required といった擬似クラスも用意されています。両者の違いは、属性セレクタが「HTML に書かれた属性」を見るのに対し、擬似クラスは「その時点の状態」を見る点です。JavaScript で element.disabled = true と設定した場合、HTML の属性は変わらないので [disabled] にはマッチしませんが、:disabled にはマッチします。状態を扱いたいときは擬似クラス、独自の data- 属性など状態が存在しないものは属性セレクタ、と考えると迷いません。
/* 独自の data 属性で状態を持たせる例 */
[data-state="loading"] {
opacity: 0.5;
pointer-events: none;
}
/* アコーディオンの開閉状態を aria 属性で判定する */
[aria-expanded="true"] .icon {
transform: rotate(180deg);
}
アクセシビリティのために付ける aria-expanded のような属性をそのままスタイルの条件にすれば、開閉状態を表すクラスを別に管理しなくて済みます。JavaScript 側は属性を切り替えるだけでよくなり、状態の持ち場所が1か所にまとまります。
大文字・小文字を区別せずに一致させる i フラグ
属性の値は、原則として大文字・小文字が区別されます。[href$=".pdf"] と書いたセレクタは、manual.PDF という大文字のファイル名にはマッチしません。区別せずに照合したいときは、閉じ括弧の前に i を書きます。
/* .pdf も .PDF も .Pdf もまとめてマッチする */
a[href$=".pdf" i]::before {
content: "PDF ";
}
反対に、type のように HTML の仕様上もともと大文字・小文字を区別しない属性で、あえて厳密に区別させたい場合は s を書きます。実務で使う機会はほとんどありませんが、こういう指定もあると覚えておくと混乱しません。
思ったとおりに効かないときの原因
class を完全一致で書いてしまっている
<div class="card card--large"> に対して [class="card"] と書いてもマッチしません。= は属性値の全体と比べるため、card card--large という文字列全体が card と等しくない限り一致しないからです。単語として含まれているかを見たいときは [class~="card"]、単純に文字列を含むかどうかでよければ [class*="card"] を使います。もっとも、クラス名で選ぶなら .card と書くのが一番確実で読みやすいので、属性セレクタを使うのは「card-- で始まるクラスをまとめて選びたい」といった特殊なケースに限るのがおすすめです。
引用符を省略して値が壊れている
属性値の引用符は、値が識別子として妥当な形(英字・数字・ハイフン・アンダースコアの組み合わせで、数字から始まらない)であれば省略できます。[type=text] は問題なく動きますが、[href^=https://] のようにコロンやスラッシュを含む値では構文エラーになり、そのルール全体が無視されます。日本語や空白を含む値も同様です。省略できる条件を覚えるより、常に引用符で囲むと決めておくほうが安全です。
詳細度が足りずに上書きされている
属性セレクタの詳細度はクラスセレクタと同じ扱いです(0-1-0)。そのため input[type="text"](0-1-1)よりも .form input のような書き方のほうが強くなる場面があり、意図せず上書きされることがあります。効かないときは、ブラウザーの開発者ツールで対象の要素を選び、スタイルの一覧で取り消し線が引かれていないかを確認してみてください。取り消し線が付いていれば詳細度の問題、そもそも一覧に出てこなければセレクタの書き方の問題です。
属性の値がスタイルを当てたい時点で変わっている
[value="..."] でテキスト入力欄の中身に反応させようとしても、利用者が入力した内容は HTML の value 属性には反映されません。属性セレクタが見るのはあくまで HTML に書かれた属性の値です。入力中の値に応じて見た目を変えたいときは、:placeholder-shown や :valid といった状態を表す擬似クラスを使います。
まとめ
属性セレクタは、クラスを増やさずに HTML がもともと持っている情報でスタイルを当てられる書き方です。[attr] で属性の有無、[attr="値"] で完全一致、^= $= *= で前方・後方・部分一致を判定でき、外部リンクや PDF へのリンクの装飾、input の type ごとの出し分け、data- 属性や aria- 属性による状態の表現といった場面で活躍します。値の大文字・小文字を無視したいときは i を添えます。つまずいたときは、= が値の全体と比較していること、引用符を省略すると構文エラーになりうること、詳細度がクラスと同じであることの3点を思い出してみてください。