リンクの入れ子は subgrid が最適解かもしれない
はじめに
リンクの入れ子とは何かというと、以下のようなデザインです。

カード全体がリンクでクリッカブルになっていて、中のタグやカテゴリーもそれぞれがリンクになっています。ニュースやブログの投稿などでよく見るデザインだと思います。
しかし、以下のようにマークアップすることはできません。
<a href="https://example.com/posts/hello-world/">
<h2>Hello, World!</h2>
<p>...</p>
<a href="https://example.com/tag/hello/">#hello</a>
<a href="https://example.com/tag/world/">#world</a>
</a>
HTML のルール的に <a> の入れ子はダメだからです。
Subgrid を使った方法
Subgrid がまだない時代からいろいろな方法が編み出されてきましたがここでは取り上げません。ではさっそく subgrid を使った方法を解説します。
HTML マークアップ
HTML は以下のようにマークアップします。至って普通です。可能なかぎりデザインに寄せます。
<div class="card">
<a class="link" href="https://example.com/posts/hello-world/">
<h2>Hello, World!</h2>
<p>...</p>
</a>
<p class="tags">
<a href="https://example.com/tag/hello/">#hello</a>
<a href="https://example.com/tag/world/">#world</a>
</p>
</div>
Grid を使って全体のリンクを拡張
.card {
display: grid;
}
.link {
grid-row: 1 / 3;
grid-column: 1;
}
.tags {
grid-row: 2 / 3;
grid-column: 1;
}
これだけで全体のリンクがタグまで拡張されるようになります。しかし、grid-row: 2 / 3; は .link .tags のどちらも使っているので、当然被ります。.link に padding-bottom などを指定すればそれを回避できますが、.tags の高さの可変には対応できません。
Subgrid の出番
.card {
display: grid;
}
.link {
grid-row: 1 / 3;
grid-column: 1;
+ display: grid;
+ grid-template-rows: subgrid;
}
.tags {
grid-row: 2 / 3;
grid-column: 1;
}
.link に subgrid を指定して、親の .card のグリッドを使うようにします。
もし .link 内に要素が 1 つしかない場合は、これでほぼ OK ですが、今回は 2 つあるので全体のグリッド設定を 3 行にします (もしくは .link 内の要素を 1 つの要素にまとめても OK) 。
.card {
display: grid;
}
.link {
- grid-row: 1 / 3;
+ grid-row: 1 / 4;
grid-column: 1;
display: grid;
grid-template-rows: subgrid;
}
.tags {
- grid-row: 2 / 3;
+ grid-row: 3 / 4;
grid-column: 1;
}
クリッカブルエリアの調整
もう上記で基本的な部分はできましたが、.tags 全体が .link の上に被っているので、.tags の中のリンクではない部分も、下の .link がクリックできないようになっています。
.tags のポインターイベントを無効にして中のリンクだけを有効にします。
.card {
display: grid;
}
.link {
grid-row: 1 / 4;
grid-column: 1;
display: grid;
grid-template-rows: subgrid;
}
.tags {
grid-row: 3 / 4;
grid-column: 1;
+ pointer-events: none;
+
+ a {
+ pointer-events: auto;
+ }
}
リンクのパディングとカードの横並び
.link にパディングが必要なく、かつ .card が縦に並ぶ場合は以上で十分ですが、現状だと下のパディングは思ったとおりにつきません。また、カードを横並びにすると、中の要素が上下均等に配置されます。逆に .card に align-content: start; を指定すると、カード全体の高さが揃わなくなります。
以下のようにするとこの 2 つの問題は解消されます。
.card {
display: grid;
+ grid-template-rows: auto auto auto 1fr;
}
.link {
- grid-row: 1 / 4;
+ grid-row: 1 / 5;
grid-column: 1;
display: grid;
grid-template-rows: subgrid;
}
.tags {
grid-row: 3 / 4;
grid-column: 1;
pointer-events: none;
a {
pointer-events: auto;
}
}
詳しくは解説しませんが、使われない 4 行目が fr であることで全体の大きさに対して拡大し、一方 auto は中身に合わせたサイズになります。
ここで 2 つ注意が必要です。
-
.cardのグリッドに対してrow-gapを指定すると、3 行目と使われていない 4 行目の間にもギャップが出ます。要素間はマージンを使うか、.linkのpadding-bottomで調整してください。 -
.linkの左右パディングは.tagsには適用されないので、同じ値の左右パディングかマージンを.tagsにも指定してください。
おわりに
HTML のマークアップは特殊のことをしておらず、CSS のほうも最低限の .tags のポインターイベント無効しかしていないので、tab キーによるフォーカスの移動や読み上げもほぼ問題ないと思います。さらに、この方法のメリットとして、alt (Win) / option (Mac) キーを押しながらのリンク内テキストの選択もほぼそのまま行えます。以下動画があります。
CodePen には入れ子リンクがタイトルと説明文の間にある場合のサンプルもありますのでよければご参考ください。グリッドの行の位置を調整するだけですが。
KITERETZ inc. (株式会社キテレツ) ではこれからめちゃくちゃわくわくするようなプロジェクトがたくさん予定されていますので、ご興味がある方はぜひお声がけください!特にデザイナーとディレクターを募集しています!
Discussion