All rules

Sheath rule

best-practices-no-nested-interactive

Interactive elements must not be nested inside other interactive elements.
Package
Core
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> (with href; 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> (except type="hidden")
  • <audio> (with controls)
  • <video> (with controls)
  • <img> (with usemap)
  • 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 its tabindex; a role="button" without one is not currently detected.
  • A negative tabindex does not make an element generally interactive, but the HTML content models for <button> and <a> prohibit any descendant with a tabindex attribute, regardless of its value. An <a> also cannot contain another <a>. These descendant restrictions apply even when the outer anchor has no href; href only determines whether an anchor is itself interactive content.
  • tabindex follows HTML integer parsing. Leading ASCII whitespace is ignored, and values with a non-integer suffix such as 1.5 still have the positive integer prefix 1.
  • A dynamic for attribute 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

#Related Rules