Sheath rule
best-practices-no-nested-interactive
Interactive elements must not be nested inside other interactive elements.
- Package
- Core
- Category
- Best Practices
- Default severity
- error by default
- Auto-fix
- Manual fix
#Why
A link inside a button gives one region two activation targets. The browser may activate either target, and a keyboard user gets two tab stops for what looks like one control. The HTML content model forbids the nesting, so browsers may reparent the elements while parsing and produce a DOM that does not match the template.
#Examples
#Bad
<!-- Link inside button -->
<button>
<a href="/profile">View Profile</a>
</button>
<!-- Button inside link -->
<a href="/page">
Click here or <button>this button</button>
</a>
<!-- Nested buttons -->
<button>
Outer
<button>Inner</button>
</button>
<!-- Form control inside button -->
<button>
<input type="checkbox"> Agree
</button>
<!-- Interactive inside link -->
<a href="/page">
<select>
<option>Option 1</option>
</select>
</a>
<!-- Label wrapping two controls: implicit labeling covers exactly one -->
<label>
Range <input type="text" name="from"> <input type="text" name="to">
</label>
<!-- Any tabindex descendant is prohibited inside buttons and links -->
<a href="/page">
<span tabindex="-2">annotation</span>
</a>
<!-- An anchor cannot contain another anchor, even without href -->
<a href="/page">
<a>Placeholder</a>
</a>
#Good
<!-- Separate interactive elements -->
<a href="/profile">View Profile</a>
<button>Edit</button>
<!-- Button with non-interactive content -->
<button>
<span class="icon">★</span>
Save
</button>
<!-- Link with icon -->
<a href="/page">
<i class="icon-arrow"></i>
Go to page
</a>
<!-- Label with for attribute (recommended) -->
<label for="agree">Agree to terms</label>
<input type="checkbox" id="agree">
<!-- Label wrapping the one control it labels (implicit association) -->
<label>
Agree to terms <input type="checkbox" name="agree">
</label>
<!-- Buttons are labelable controls too -->
<label>
Save <button>Go</button>
</label>
<!-- Anchor without href is a placeholder, not interactive content -->
<button>
<a>Not a link yet</a>
</button>
#Interactive Elements
#Always Interactive
<a>(withhref; without it an anchor is a placeholder and does not count)<button><details><embed><iframe><label>(wrapping its one labelable control, including a<button>, is the sanctioned implicit-label pattern and is not reported)<select><textarea>
#Conditionally Interactive
<input>(excepttype="hidden")<audio>(withcontrols)<video>(withcontrols)<img>(withusemap)- Elements with
tabindex="0"or positive tabindex
#Notes
- Coverage uses the tag names and attributes above. An element made
interactive purely through ARIA, such as a
<div role="button" tabindex="0">with a click handler, is only caught via itstabindex; arole="button"without one is not currently detected. - A negative
tabindexdoes not make an element generally interactive, but the HTML content models for<button>and<a>prohibit any descendant with atabindexattribute, regardless of its value. An<a>also cannot contain another<a>. These descendant restrictions apply even when the outer anchor has nohref;hrefonly determines whether an anchor is itself interactive content. tabindexfollows HTML integer parsing. Leading ASCII whitespace is ignored, and values with a non-integer suffix such as1.5still have the positive integer prefix1.- A dynamic
forattribute does not exempt every control nested in a label. When multiple labelable controls are present, the label is reported once because at most one runtime target can be its labeled control. - Label content restrictions cover every labelable element, including the
normally non-interactive
<meter>,<output>, and<progress>elements. Only the label's own labeled control may be nested inside it.
#References
- HTML Spec: Interactive Content - Definition of interactive content
- HTML Spec: The button element - Button descendant restrictions
- HTML Spec: The a element - Anchor descendant restrictions
- Deque: Nested Interactive Elements - Accessibility implications of nesting
#Related Rules
- a11y-no-heading-inside-button - No headings in buttons
- best-practices-button-type - Button type attribute