aタグにaタグを入れ子にしたい!(がHTMLはそれを許さない)

2025/01/05に公開
3

はじめに

皆さんはaタグにaタグを入れ子にしたいと思ったことはありませんか?もしかしたら意外とないかもしれないですね笑🤓
例えばQiitaの記事カードのUIなどは複数のリンク先を内包するカードになっていますが、カード全体もaタグとして機能しています。
QiitaのカードUI
(出典: https://qiita.com/

こういった場合に単純に下記のようにマークアップすることはできません。

<a href="/article/1">
  <a href="/user/1">@UserName</a>
  <a href="/article/1">記事のタイトル</a>
</a>

Next.jsの場合、開発環境では下記のようにエラーになります。

In HTML, <a> cannot be a descendant of <a>. This will cause a hydration error.

Next.jsのローカル環境のエラー画像、エラー内容「In HTML, <a> cannot be a descendant of <a>. This will cause a hydration error.」

理由は、aタグは「インタラクティブコンテンツ」として扱われ、入れ子にすることが許可されていないからです。

参考: https://developer.mozilla.org/ja/docs/Web/HTML/Element/a#技術的概要

解決方法

親要素にposition: relativeを付与しつつ、カード全体の遷移先としたいaタグにinset: 0px; position: absolute;を付与します。
こうすることでHTMLの規約を守った上で実現したい構造のUIを実装できます。Qiitaでも同じ方法が使われているようです👀

<article style={{ position: "relative" }}>
  <a href="/article/1" style={{ position: "absolute", inset: 0 }} tabIndex={-1}></a>
  <a href="/user/1" style={{ position: 'relative', zIndex: 1 }}>@UserName</a>
  <a href="/article/1" style={{ position: 'relative', zIndex: 1 }}>記事のタイトル</a>
</article>

tabIndex=-1は今回は記事タイトルと遷移先が重複しているためフォーカス順序を煩雑にしないよう付与しています。
※コンテンツごとのz-indexを調整する必要があります。

頑張ってまでaタグで実装する意味はあるのか?

上記のようなことをしてまでaタグでマークアップしなくても、div使えばいいじゃんと思った方もいるかもしれません。
しかし、安易にaタグ以外を使うのは避けた方が良いと考えています。
理由はaタグにはブラウザ標準で実装された機能がいくつもあり、それらをすべて再現するのは不可能だからです。
例えばChromeの場合、aタグはhoverをしたときに遷移先のURLを画面左下に表示してくれます。

ZennのUI、aタグをhoverした際に遷移先URLが表示されるブラウザの機能を示している
またmacの場合、commandを押しながらクリックすると別タブで開いたりなどの挙動もあります。

divタグを使って代用した場合はこうした機能が失われるため極力aタグを使用したマークアップをお勧めします🙌

本記事を書いたことでXにて教えていただいたのですが、Open UI Community Groupで、仕様の策定が検討されているProposalの一つに、Link Area Delegationというものがあるみたいです。
これを利用すれば本記事のようなややハッキーな方法を採用せずとも、カード全体をリンクとして扱ったりすることができます。
しかし2025/01/06現在ではまだ検討中のステータスの様なので、随時情報を追っていけると良さそうですね👀

参考:
https://azukiazusa.dev/blog/link-area-delegation-proposal/#link-area-delegation-の概要
https://blog.sakupi01.com/dev/articles/proposal-link-area-delegation

株式会社ZOZO

Discussion

かわりくかわりく

ちょうどこのような実装がしたかったので、とても参考になりました!ありがとうございます!

記事の解決方法通りに実装させていただいたところ、articleに含まれる以下の二つのリンクが機能しなくなりました...(正確には、article全体をaタグが覆うようになっているため、そっちが発火しちゃう)

  <a href="/user/1">@UserName</a>
  <a href="/article/1">記事のタイトル</a>

本記事と、Qiitaのカードの実装も見てみたのですが、どうやって回避しているのかわかりませんでした...
回避方法まで追記いただけるととても助かります!


追記:↑こういう感じで、ネストしているaタグにフォーカスができないです...

ツチヤカイタツチヤカイタ

コメントありがとうございます!おっしゃる通りでz-indexを調整する必要があります!
省略してしまってましたが記事にも追記しました🙏