しっかり学ぶ、a要素 第1回 a要素の書き方とCSS

Webの特徴的な操作のひとつに、ハイパーリンクがあります。この記事ではハイパーリンクを実現するa要素について紹介します。まずはその記述の特徴を見てみましょう。

発行

著者 矢倉 眞隆 フロントエンド・エンジニア
しっかり学ぶ、a要素 シリーズの記事一覧

このシリーズは仕様の変更に基づき、メンテナンスしています。(最終更新2022年4月)

ハイパーリンクを記すa要素

このシリーズでは、HTMLのa要素について詳しく紹介します。

詳細に入る前に、いまいちどa要素の目的についておさらいしましょう。

a要素はリンク、より厳密にいえばハイパーリンクを記述するための要素です。ハイパーリンクは、現在のHTMLの仕様書では次のように定義されています

These are links to other resources that are generally exposed to the user by the user agent so that the user can cause the user agent to navigate to those resources, e.g. to visit them in a browser or download them.

他のリソースへのリンクのうち、そのリソースへナビゲート(ページの移動やダウンロードなど)ができるよう、ユーザーエージェントによってその効果を表現したものが、ハイパーリンクです。

クリックしたら別のリソースに移動したり、何かをダウンロードしたり。シンプルな操作ですが、私たちはこれにより情報を得る新しい手段を手にしました。HTML、もといWebを語る際に、ハイパーリンク*は外せないのです。

*注:ハイパーリンク

本記事ではこれ以降、ハイパーリンクを「リンク」と表記します。

a要素の基本

a要素は基本的に、文章の一部をくくってリンクを表現します。

Web開発のお供に<a href="https://www.codegrid.net/">CodeGrid</a>を。

href属性の中にURLを記述すると、その文字列部分がリンクになり、クリックやタップで移動できるようになります。こう見ると、リンクをリンクたらしめているのは、a要素というよりはhref属性なのかもしれません。

とはいえ、href属性のないa要素も記述できるようになっています。hrefのないa要素は、リンクのプレースホルダを表すとされています。

例として仕様書にも挙げられているのが、Webサイトのナビゲーションにある「現在地」です。リンクはその見た目や挙動から「今見ているものと違う箇所に移動する」と見られがちです。Webサイトの構造と、現在どこにいるかを伝えるために、現在地だけリンクしないというつくりのページが存在します。

そういった、「他のページではリンクとして使われているが、何らかの都合でリンクとして機能させたくない場合」に、hrefを省いたa要素が認められています。次は、HTML仕様書のマークアップ例です。

<nav>
 <ul>
  <li> <a href="/">Home</a> </li>
  <li> <a href="/news">News</a> </li>
  <li> <a>Examples</a> </li>
  <li> <a href="/legal">Legal</a> </li>
 </ul>
</nav>

後述するCSSの書き方もあるため現在もそこまで広まっている印象はありませんが、一部だけをspan要素にするなどの必要がないため、いくぶんスマートに見えます。

a要素内に書けるもの、a要素を書けるところ

先ほど挙げた例では、a要素内にプレーンテキストのみが書かれていました。しかしもちろん、a要素内には他の要素も含められます。

ある要素と、その中に記述できるもの(要素、テキストなど)との関係を、「コンテントモデル(content models)」と呼びます。

以前のa要素のコンテントモデルは、インライン要素に限定されていました。emやstrong、spanなどはインライン要素なのでa要素の中に記述できますが、pやdiv、h1などはブロック要素なので、a要素の中に記述できませんでした。

<a href="...">
  <p>以前はこう書けませんでした。</p>
</a>

HTML5以降は、各要素のコンテントモデルが細かくなり、インライン要素・ブロック要素といった大きなカテゴリはなくなりました。a要素のコンテントモデルも更新され、それまでブロック要素と呼ばれていた要素も含められるようになりました。

たとえば、商品一覧画面で、各商品が次のようにマークアップされているとします。

<div class="card-item item">
  <h3 class="item-title">製品名</h3>
  <img class="item-image" ...>
  <p class="item-description">...</p>
</div>

商品一覧をカード状のUIとして提供する場合、このブロック全体をリンクとできればうれしいです。しかし、HTML4時代のHTMLでは、a要素はブロックを囲めませんでした。結果、商品名だけにリンクを張ったり、画像や詳細にそれぞれリンクを張ったり、カードのdiv要素内をクリックしたらリンク先に移動するようなJavaScriptを書いたりといったことをしていました。

HTML5以降ではそういった制限がなくなったため、a要素でシンプルに商品のブロックを包めます。

<a href="/products?pid=****">
  <div class="card-item item">
    <h3 class="item-title">製品名</h3>
    <img class="item-image" ...>
    <p class="item-description">...</p>
  </div>
</a>

ただし、子孫に別のa要素やbutton要素など、操作を受け付ける要素を置くことはできないとされています。先ほどの例でいうと、商品一覧のカードに「カートに追加」というボタンは置けないことになります。

<a href="/products?pid=****">
  <div class="card-item item">
    ...
    <button class="item-add" ...>カートに追加</button>
  </div>
</a>

ここでの「置けない」、つまり「書けない」というのは製作者の要件で、ブラウザは特にa要素の中にある別のa要素やボタンを無効にすることはありません。とはいえ、押す場所によってボタンの挙動が違うのはUIとしては親切ではないように感じます。ブロックを包むのであれば、内側にはボタンやリンクを置かないようにしましょう。

a要素のコンテントモデルとHTMLのトランスパレントコンテント

HTML5以降はブロック要素となっていたものを囲めるようになったと述べましたが、具体的な要素は書きませんでした。これはa要素のコンテントモデルが少し特殊だからです。

現在のHTMLで、a要素のコンテントモデルは「トランスパレントコンテント(transparent content)」というものになっています。これは、それ自身のコンテントモデルに決まったカテゴリを定めず、親のコンテントモデルを継承するというものです。ですので、a要素がブロックを包むことができるかは、そのa要素の親次第ということになります。詳細は後述しますが、基本的には包むことができると考えて大丈夫です。

たとえば、p要素を囲ったa要素が、div要素の直下にあるとします。

<div>
  <a ...>
    <p>こんにちは。</p>
  </a>
</div>

このマークアップが正しいかを調べるには、aの親であるdiv要素と、aの子であるp要素の関係に注目します。要は、div要素直下にp要素があるケースが、正しいかどうかをチェックすればよいのです。

<!-- コメントアウトや、ソースから削られているとして考える -->
<div>
  <!-- <a ...> -->
    <p>こんにちは。</p>
  <!-- </a> -->
</div>

div要素のコンテントモデルは、基本的にフローコンテント(flow content)とされています。フローコンテントとは基本的にbody内で使われる要素を表すカテゴリで、以前のブロック要素とインライン要素が合わさったようなものです。body内なので、head要素内に書かれるtitle要素やstyle要素などはフローコンテントではありません。

p要素はもちろん、フローコンテントになります。div要素の直下にp要素がある状態は、まったく問題ありません。ですので、div > a > pというマークアップも、問題ないことになります。

フローコンテントにはかなり多くの要素が属しており、また多くの要素がフローコンテントもしくはフレージングコンテント(phrasing content)(従来のインライン要素とおおよそ同等です)をコンテントモデルとしているため、a要素はかなり多くの要素を囲めるようになっています。

HTML仕様書にある各カテゴリのベン図から。フローコンテントは一部を除き、ほとんどのカテゴリを包含していることがわかる。

a要素を書けないところ

それでは、逆はどうでしょうか。次のマークアップが正しいかをチェックしてみます。

<p>
  <a ...>
    <div>さようなら。</div>
  </a>
</p>

先ほどと同じように、p要素の直下にdiv要素が書けるかを調べればよいわけです。

pのコンテントモデルは、フレージングコンテントという、昔でいうとインライン要素に近いものです。しかしdiv要素はフレージングコンテントではないため、pの直下にdivは書けません。よって、p直下のaもdivを包めず、これはエラーになります。

また、要素によっては、コンテントモデルの制約がとても厳しいものがあります。そういった要素においては、a要素をその中に記述できないケースがあります。たとえば、ul要素とli要素です。

「a要素でli要素を囲めないかな?」と思った方はいないでしょうか。次のようなマークアップです。

<ul>
  <a>
    <li>...</li>
  </a>
  ...
</ul>

残念ながら、これはエラーになります。ulのコンテントモデルは、「0個以上のli要素」と、かなり限定されているからです。

書いてしまった場合はどうなる?

p > a > divul > a > liはエラーになると書きましたが、これも制作者に対する要件です。もしそれを知らずマークアップしてしまった場合、どうなるのでしょうか。

たとえば、p > a > divは、次のようなDOMツリーとなってしまいます。

├ P
│ └ A
├ DIV
│ └ A
├ A
├ P

入れ子にしたはずなのに、div要素がpの兄弟要素となってしまっています。かなり不思議に思えますが、p要素の終了タグを省略できる仕様との兼ね合いで、このような結果となってしまいます。

ul > a > liについては、簡単に試したところ特に問題はありませんでしたが、li要素の中に入る要素によっては、p > a > divのようにおかしなDOMツリーになるかもしれません。

リンクとCSS

ここまではa要素の書き方について紹介しました。ここからは、リンクの装飾に使うCSSの機能について紹介します。

CSS仕様には、リンクに特化したセレクタがいくつか存在します。代表的なものは、:link:visitedでしょう。:link未訪問を、:visited訪問済みのリンクにマッチします。

:visitedについては、悪意のあるスクリプトが訪問済みのページを取得する手段として使われ、プライバシーに関する懸念があったため、使えるプロパティが次のものに限定されています。

  • color
  • background-color
  • border-color とサブプロパティ(border-top-colorなど)
  • outline-color
  • column-rule-color
  • fill-color
  • stroke-color

以前はbackground-imageを使い、未訪問と訪問済みリンクに別のアイコンを指定するといったCSSがありましたが、現在はできなくなっています。

注:Chrome 136以降の:visitedの変化

Chrome 136からは、:visitedの適用条件がさらに変化しました。訪問済みとして表示されるのは、同じサイト(オリジン)内でのリンクのみとなり、他サイトへのリンクは未訪問として表示されるようになっています。これにより、訪問履歴を推測するリスクをより低減する仕組みが導入されています。詳細は別記事で取り上げます。

すべてのリンクを示す:any-link

:linkはその名に反し、未訪問のリンクを表します。このためリンクすべてにマッチするセレクタは、:link:visitedを併記するしかありませんでした。

/* これくらいならまだ書けそうだが…… */
:link, :visited { ... }

/* 他の擬似クラスと組み合わせる場合にとても面倒 */
:link:hover, :visited:hover, :link:active, :visited:active { ... }

書きづらさからか、要素型セレクタを使うケースがほとんどのように思います。

/* 要素型セレクタを使ったほうが楽 */
a { ... }
a:hover, a:active { ... }

しかし、aだけの要素型セレクタでは、プレースホルダとしてのリンクにもマッチしてしまいます。a.currentとクラスをつけたり、a:not([href]):not()擬似クラスを使い打ち消すなどしないといけませんが、だいぶ面倒です。

Selectors Level 4仕様では新たに、未訪問と訪問済みいずれのリンクにもマッチするセレクタとして、:any-link擬似クラスが定義されました。

:any-linkを使うと、aという要素型セレクタを使うのと同じ感覚で、リンクのみに適用されるスタイルを記述できます。プレースホルダとしてのa要素にはスタイルが適用されないので、純粋にリンクのスタイルを指定できます。

:any-link { ... }
:any-link:hover, :any-link:active { ... }

2018年の公開当時はベンダー接頭辞付きの実装が先行しており、接頭辞なしの対応はブラウザで広がり始めた頃でした。:any-linkをサポートしないInternet Explorerへの対応も求められていた時代で、気軽に使える状況ではありませんでした。その後、各ブラウザで実装が進み、IEのサポートも終了しました。現在は主要ブラウザで安心して使えるようになっています。

aによる要素型セレクタはリンクのスタイル指定の慣習として長く使われており、「未訪問・訪問済みを問わずリンクすべてにマッチする」という:any-linkの役割とも重なるため、使い所は見えにくいかもしれません。ただし、リンクのプレースホルダには適用されないという点で、:any-linkはより意味の明確なセレクタといえます。

リンクに関するその他の擬似クラス

Selectors Level 4にはかつて、現在のページのURLとリンク先のURLが一致するリンクにマッチする:local-linkという擬似クラスが定義されていました。ナビゲーションの現在地表示などに使い所があると考えられていましたが、ブラウザの実装がなく、現在はSelectors Level 5に移動されています

2025年には、View Transition APIに関連する機能として考案されたCSS Route Matchingのドラフトが公開されました。特定のURLにマッチしてスタイルを適用する仕組みが定義されており、:local-linkに近い機能も含まれています。

まとめ

今回はリンクとa要素について、HTMLとCSSの観点から紹介しました。

とはいえ、リンクをリンクたらしめる、「クリックして移動」という性質については何ら説明していません。次回はその移動に関するあれこれを紹介しようと思います。