D
P
0

HTML & CSS

A Nested `<a>` Inside Another `<a>` Silently Collapsed My Card Grid

June 9, 2026·4 min read
A Nested `<a>` Inside Another `<a>` Silently Collapsed My Card Grid

This is the bug that had me blaming CSS for a solid hour when CSS was never the problem. On an editorial site I built, the article card grid suddenly collapsed: each card shrank to a column about one character wide, and every word in the title dropped onto its own line, stacked vertically. The strange part — the cover image inside the card still had a normal width. Only the title and byline were wrecked.

I poked at grid-template-columns, min-width, flex-basis, all of it. Nothing moved. The root cause turned out to be in the HTML, not the CSS: I had accidentally nested an <a> inside an <a>, and the browser quietly tore that structure apart.

Why a nested anchor destroys the grid

HTML5 forbids nested anchors. An <a> is interactive content, and the spec doesn't allow interactive content inside other interactive content. The catch is that the browser doesn't throw an error — it silently "repairs" your markup.

My card looked roughly like this: the whole card was wrapped in an <a> to make it clickable, and inside it sat another <a> for the author's name (a link to the author page). When the parser hit that second <a>, it implicitly closed the first <a> right at that point. As a result, everything after the author link — the title, the byline, the rest — was no longer inside the wrapping anchor. It got promoted to being a direct child of the grid element.

And that's where the grid collapses. A grid child with no explicit placement defaults to col-span-1. So a title that used to span the full card was now jammed into a single narrow column track, and its text broke apart word by word.

The fastest diagnostic to prove it: check the parent of the title element in DevTools.

// If the outer link got unwrapped, the h3's parent is no longer <a>
document.querySelector(".card h3").parentElement.tagName;
// Expected: "A"  → Reality: "ARTICLE"  (the wrapping anchor is gone)

The moment that returns "ARTICLE" instead of "A", it's confirmed: the browser has unwrapped my outer anchor.

The broken markup

<a href="/article/slug" class="card">
  <img src="/cover.webp" alt="" />
  <h3>A long article headline</h3>
  <!-- THIS is the culprit: an anchor inside an anchor -->
  <a href="/author/jane">Jane Doe</a>
  <time>June 9, 2026</time>
</a>

The instant the parser reaches <a href="/author/jane">, it closes the .card anchor. The <h3>, the <time>, and the author link become direct siblings inside the grid — no longer children of the clickable card.

We still need two distinct click targets: clicking the card goes to the article, clicking the name goes to the author page. The trick is to not use two anchors. Render the inner link as a non-interactive element that we give link behavior to by hand:

<a href="/article/slug" class="card">
  <img src="/cover.webp" alt="" />
  <h3>A long article headline</h3>
  <!-- Not an <a>, so the outer anchor is never unwrapped -->
  <span data-href="/author/jane" role="link" tabindex="0">Jane Doe</span>
  <time>June 9, 2026</time>
</a>

Because a <span> isn't interactive content, the browser no longer force-closes the outer anchor. The structure stays intact and the grid renders normally again. Then we wire up one delegated handler on the document to bring data-href to life:

// Click on a data-href element → navigate, and don't trigger the card link
document.addEventListener("click", (e) => {
  const link = e.target.closest("[data-href]");
  if (!link) return;
  e.preventDefault();
  e.stopPropagation(); // stop the card anchor from also firing
  window.location.href = link.dataset.href;
});
 
// Keyboard: a span with role="link" must respond to Enter and Space
document.addEventListener("keydown", (e) => {
  if (e.key !== "Enter" && e.key !== " ") return;
  const link = e.target.closest("[data-href]");
  if (!link) return;
  e.preventDefault();
  e.stopPropagation();
  window.location.href = link.dataset.href;
});

stopPropagation() matters: without it, clicking the author name would bubble up to the card anchor and take the user to the article instead of the author page. The role="link" and tabindex="0" restore the accessibility we lost by dropping the real <a> — the element becomes keyboard-focusable and is announced as a link by screen readers, and the keydown handler covers Enter/Space the way a real anchor would.

A few extra notes

  • The fastest diagnostic still lives in DevTools: if an inner element's parentElement.tagName isn't what you expect, an interactive element was probably unwrapped by the browser. The same applies to a <button> inside an <a>, or an <a> inside a <button>.
  • The "big clickable card with one small link inside it" pattern is common. An alternative fix: make the card not an anchor (use an overlay pseudo-element ::after with position: absolute; inset: 0), then keep the author link as a real <a> given position: relative; z-index: 1 so it sits above the overlay. That preserves true anchor semantics for both.
  • Always validate the HTML when a layout breaks "for no reason." The validator flags it immediately as "Element a not allowed as child of element a" — a far faster lead than spending an hour spelunking through CSS.

The takeaway: if a grid collapses and your CSS looks correct, check whether the browser has quietly rewritten your DOM. A nested anchor is one of the subtlest ways markup that "looks right" turns into a completely different structure once it's parsed.