Skip to content

Interactive controls need to be separate

  • Rule: axe/nested-interactive
  • Checks: Interactive controls must not be nested
  • WCAG: 4.1.2
  • Fix class: manual: A fix needs a person to work out what to change, beyond authored text or a visual decision.
  • People who use a screen reader or a braille display, most of them blind.
  • People who use a keyboard or a switch device instead of a mouse.
Section titled “A clickable card is a <button> or role="button" element with links or buttons inside”

Make the controls siblings. Put one link on the card’s heading and, if the whole card should be clickable, stretch that link’s clickable area over the card with a CSS pseudo-element, keeping the other controls outside the link and above it.

<article class="card">
<h2><a href="/recipes/tomato-soup" class="card-link">Tomato soup</a></h2>
<p>Ready in 20 minutes.</p>
<button type="button">Save recipe</button>
</article>

Use one element: a link if it goes to another page or place, a button if it acts on this page. A button inside a link is invalid HTML too, though this rule does not report it.

<a href="/checkout" class="button">Checkout</a>

A tab or a listbox option contains a close or delete button

Section titled “A tab or a listbox option contains a close or delete button”

Take the button out of the item and out of the tablist or listbox, which may hold only their items: place it next to the widget, or use a grid, whose cells can hold several controls. A Delete key shortcut can add to the button but not replace it. These roles present their content as plain text, so assistive technology does not announce the inner button.

Adding tabindex="-1" or aria-hidden="true" to the inner control

Section titled “Adding tabindex="-1" or aria-hidden="true" to the inner control”

axe still reports either. tabindex="-1" drops the control from the tab order, and aria-hidden="true" hides it from assistive technology while it can still take focus.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

Section titled “A button component wraps a router link passed as its children”

Render one element. Give the button component a way to render as a link, as many component libraries do with an as, asChild or component prop, or style the link itself as a button.

function ButtonLink({ href, children, ...props }) {
return (
<a href={href} className="button" {...props}>
{children}
</a>
);
}

Calling event.stopPropagation() in the inner control’s onClick

Section titled “Calling event.stopPropagation() in the inner control’s onClick”

Clicks then reach only the inner control, but the controls are still nested: axe still reports it, and the outer control still presents the inner one as plain text.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.