Avoid Nested Interactive Controls

No Comments
Avoid nested interactive controls

Element Code: AC-033

TL;DR: An interactive element nested inside another interactive element, a button inside a link, a link inside a button, breaks keyboard access and screen reader announcements. Worse, nested anchors get silently split apart by the HTML parser, so the markup crawlers see is not the markup you wrote. Flatten it: one control per interaction.
Check
AC-033, Accessibility
Affects
Links, buttons, widgets
Severity
Serious (axe rating)
Detection
axe nested-interactive
Usual suspect
Card components

What counts as a nested interactive control

Any focusable, activatable element sitting inside another one: a <button> inside an <a>, an <a> inside a <button>, an anchor inside an anchor, a link inside a <summary>, form controls inside a clickable label that maps to a different control, or anything with tabindex="0" or an interactive ARIA role (button, link, checkbox, tab) placed inside an element that already has one. Axe flags all of these under its nested-interactive rule and rates the issue serious, and this check mirrors that logic.

The HTML spec is blunt about it: the content model for <a> and <button> forbids interactive content inside them. This is not a style preference, it is invalid markup with runtime consequences.

Why it breaks things

One tab stop, two actions. Keyboard users get a single focus stop where two behaviors live. Which one fires on Enter? Depends on the browser and the event wiring. Some inner controls become unreachable by keyboard entirely, which fails WCAG 2.1.1 (Keyboard).

Screen readers pick one name. Assistive technology has to compute a single role and name for the focused thing. A "Read more" link wrapping a card that also contains a "Save" button often gets announced as one link whose name is the entire card text, with the button never announced at all.

The parser rewrites nested anchors. This is the part with direct SEO teeth. Browsers cannot represent an <a> inside an <a>, so the HTML parser closes the first anchor the moment it meets the second. Your DOM ends up with two sibling links whose boundaries and anchor text are not what your template says. Googlebot renders pages with the same parsing rules, so link extraction and anchor text run against the mangled version. I have watched an "entire card is a link" template quietly turn its card titles into empty anchors this way, and nobody had looked at the rendered DOM in a year.

Before: nested controls <a href="/product"> (card) <img> <h3> <button> Button trapped inside the link: one tab stop, two competing actions, screen reader announces the link only axe: nested-interactive (serious) Invalid per the HTML content model After: flattened structure <div class="card"> <a> title <img> <button> Link and button are siblings: two tab stops, each announced correctly, CSS stretches the link across the card Valid HTML, clean anchor text Crawlers see the links you intended

Where I keep finding it

The same handful of patterns account for nearly every instance in real audits.

PatternWhat breaksFix
Whole card wrapped in <a> with a button insideButton unreachable or unannounced; giant anchor textStretched link on the title, button as a sibling
<a> inside <a> (card link plus inner tag links)Parser auto-closes the outer anchor and splits the DOMRestructure; nested anchors cannot exist as written
<button> wrapping a link, or vice versaInvalid HTML, double activation, focus confusionPick one control per action, move the other out
Clickable <tr> via onclick, plus real links in cellsRow swallows clicks; AT sees a plain row, not a controlOne real link in the row; extend its hit area with CSS
tabindex="0" on children of a controlTwo tab stops for one action, phantom focus targetsRemove inner tabindex; one focusable element per control

How to detect it

  • axe DevTools: the nested-interactive rule names each offending node with a CSS selector. Run it on your card grid, your navigation, and one article template and you will have covered most of the site's components.
  • Lighthouse: the accessibility category runs a subset of axe, so nested controls surface in plain Chrome DevTools with no extension.
  • Keyboard test: tab through a card component. If you can see a button you can never land on, or one tab stop visibly covers two actions, you have found it without any tooling.
  • Source vs rendered DOM: for the nested anchor case specifically, compare view-source with the Elements panel. If anchors sit differently in the two, the parser has been rewriting your markup on every page load.
  • Screaming Frog: a Custom Extraction with an XPath like //a//a | //a//button | //button//a inventories the problem across the whole site in one crawl.

How to fix it, the pattern that works

For cards, the stretched link pattern solves this cleanly and it is what I put in every design system review. The card container is a positioned <div>. The title holds the only anchor. A pseudo element on that anchor (position:absolute; inset:0) stretches its clickable area across the whole card. Secondary actions like Save or Add to cart are sibling buttons layered above the overlay with position:relative and a higher z-index.

  1. Inventory instances with axe plus the Screaming Frog extraction above.
  2. For each, name the primary action. That element keeps the anchor or button; everything else becomes a sibling.
  3. Apply the stretched link pattern where a large hit area is wanted. Do not re-wrap the container in an anchor.
  4. Strip stray tabindex attributes and interactive ARIA roles from children of controls.
  5. Re-run axe, then re-do the keyboard pass: every visible control gets its own tab stop, in a sensible order, and Enter or Space activates exactly the thing that is focused.

What good looks like: valid markup where every interactive element is a leaf, anchor text that matches the visible link label, and a rendered DOM identical in structure to your source. Boring, predictable, crawlable.

DO

  • Keep one interactive control per action, as siblings
  • Use the stretched link pattern for large card hit areas
  • Give the anchor the card title as its text, not the whole card
  • Tab through components by hand after every refactor
  • Compare source and rendered DOM when anchors behave oddly

DON'T

  • Wrap entire cards in an anchor that contains buttons or links
  • Nest an anchor inside an anchor, ever: the parser will not let it survive
  • Add onclick to rows or containers that hold real links
  • Sprinkle tabindex="0" inside existing controls
  • Assume passing axe on one page clears a component used on two hundred

FAQ

Is this an SEO issue or just an accessibility issue?
Mostly accessibility, with one real SEO exception: nested anchors. The parser splits those, which changes link boundaries and anchor text in the rendered DOM that Googlebot processes. If your internal linking relies on card links, that is worth caring about beyond compliance.
Why does axe rate nested-interactive as serious rather than minor?
Because the failure mode is losing access to a function, not cosmetic noise. A keyboard or screen reader user may be unable to reach the inner control at all, which fails WCAG 2.1.1. Issues that block task completion sit above issues that merely annoy.
What is the right markup for a clickable card with a Save button?
Positioned div container. One anchor on the card title, stretched across the card with an absolutely positioned pseudo element. The Save button is a sibling of the anchor, layered above the stretched area with position relative and a higher z-index. Two tab stops, both announced correctly, valid HTML.
Does a link inside a summary element count?
Yes. The <summary> element is itself an interactive control (it toggles the disclosure), so a link inside it is a nested interactive and axe flags it. Put links in the expanded content of the details element instead, and keep the summary as a plain toggle label.

One flagged component usually means a system-wide pattern

Nested controls ship from design systems, so a single axe finding often multiplies across every listing page you have. An Advanced SEO Audit traces flagged components to their templates and hands your developers exact, prioritized fixes.

Book an Advanced SEO Audit

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog