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.
The fix: inner link as a <span> with data-href, not an <a>
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.tagNameisn'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
::afterwithposition: absolute; inset: 0), then keep the author link as a real<a>givenposition: relative; z-index: 1so 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.
