🖇️

リンクの入れ子は subgrid が最適解かもしれない

2024/03/28に公開

はじめに

リンクの入れ子とは何かというと、以下のようなデザインです。

リンクが入れ子になっているようなデザイン

カード全体がリンクでクリッカブルになっていて、中のタグやカテゴリーもそれぞれがリンクになっています。ニュースやブログの投稿などでよく見るデザインだと思います。

しかし、以下のようにマークアップすることはできません。

html
<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 は以下のようにマークアップします。至って普通です。可能なかぎりデザインに寄せます。

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 を使って全体のリンクを拡張

css
.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 のどちらも使っているので、当然被ります。.linkpadding-bottom などを指定すればそれを回避できますが、.tags の高さの可変には対応できません。

Subgrid の出番

css
 .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;
 }

.linksubgrid を指定して、親の .card のグリッドを使うようにします。

もし .link 内に要素が 1 つしかない場合は、これでほぼ OK ですが、今回は 2 つあるので全体のグリッド設定を 3 行にします (もしくは .link 内の要素を 1 つの要素にまとめても OK) 。

css
 .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 のポインターイベントを無効にして中のリンクだけを有効にします。

css
 .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 が縦に並ぶ場合は以上で十分ですが、現状だと下のパディングは思ったとおりにつきません。また、カードを横並びにすると、中の要素が上下均等に配置されます。逆に .cardalign-content: start; を指定すると、カード全体の高さが揃わなくなります。

以下のようにするとこの 2 つの問題は解消されます。

css
 .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 つ注意が必要です。

  1. .card のグリッドに対して row-gap を指定すると、3 行目と使われていない 4 行目の間にもギャップが出ます。要素間はマージンを使うか、.linkpadding-bottom で調整してください。
  2. .link の左右パディングは .tags には適用されないので、同じ値の左右パディングかマージンを .tags にも指定してください。

おわりに

HTML のマークアップは特殊のことをしておらず、CSS のほうも最低限の .tags のポインターイベント無効しかしていないので、tab キーによるフォーカスの移動や読み上げもほぼ問題ないと思います。さらに、この方法のメリットとして、alt (Win) / option (Mac) キーを押しながらのリンク内テキストの選択もほぼそのまま行えます。以下動画があります。

https://x.com/ixkaito/status/1772961894107685192?s=20

CodePen には入れ子リンクがタイトルと説明文の間にある場合のサンプルもありますのでよければご参考ください。グリッドの行の位置を調整するだけですが。

https://codepen.io/ixkaito/pen/WNWERJg

KITERETZ inc. (株式会社キテレツ) ではこれからめちゃくちゃわくわくするようなプロジェクトがたくさん予定されていますので、ご興味がある方はぜひお声がけください!特にデザイナーとディレクターを募集しています!

https://kiteretz.com/careers/

Discussion