gaurav5430
gaurav5430

Reputation: 13902

HTML Validation: Why is it not valid to put an interactive element inside an interactive element?

Disclaimer: I understand that it is not valid HTML. I am trying to understand why is it not allowed?

W3C suggests that an interactive element like button or a mustn't contain another interactive element.

I could find a lot of resources mentioning this rule and some workarounds, also some resources related to how this impacts accessibility and screenreaders, but almost all of those resources talk about the fact that this is a requirement but do not explain why.

https://adrianroselli.com/2016/12/be-wary-of-nesting-roles.html

https://codepen.io/vloux/pen/wXGyOv

Nesting <a> inside <button> doesn't work in Firefox

https://github.com/dequelabs/axe-core/issues/601

I wasn't really able to find an explanation for why is it not allowed? does it lead to any usability problems?

This is a related question: Why should interactive element not be used within an anchor?

The accepted answer is satisfactory but is not enough to make this rule a requirement. The described situation can be avoided using proper event handling.

Also, if nested interactive content is invalid, how are we supposed to have something like this:

A card which is clickable as a whole, and also has a clickable secondary CTA inside it. I know a workaround would be to have a primary and secondary CTA inside the card, but shouldn't the above be allowed as well?

Here is a fiddle: https://jsfiddle.net/t9qbwas5/2/

<button type="button" class="card">
  The card itself is the primary CTA.
  <br/>
  <br/>
  some important content to read through.
  some important content to read through.
  some important content to read through.
  <div class="cta">
   Secondary CTA
  </div>
</button>

.cta {
  padding: 4px;
  background: #00a9a9;
  color: white;
  width: 80px;
  margin: auto;
  margin-top: 8px;
  margin-bottom: 8px;
}

.card {
  width: 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}

enter image description here In the above example, I am achieving this by using a clickable div inside the button, but that is not semantic (?) and also functionality wise, it is an interactive element inside another one. I am trying to understand that even if I use this workaround, is it fundamentally wrong to have nested interactive elements? is it a bad design/usability practice?

Upvotes: 5

Views: 3962

Answers (4)

Adam
Adam

Reputation: 18855

A card which is clickable as a whole, and also has a clickable secondary CTA inside it.

Although visually imaginable and technically possible, it's not accessible for assistive technologies, like screenreaders

Let's make a simple example:

<button>
    Click for action 1
    <button>Click for action 2</button>
</button>

The accessible name for the first <button> would be "Click for action1 Click for action 2". And if you define an aria-label="Click for action 1", then the inner button element would not be read at all.

If you really want to make a whole element clickable, you can perfectly use javascript and still be accessible

<div class="outer">
  <button type="button" class="card">
    The card itself is the primary CTA.
  </button>
  <br/>
  <br/>
  some important content to read through.
  some important content to read through.
  some important content to read through.

  <button class="cta">
   Secondary CTA
  </button>
</div>

<script>
$(".outer").on("click", function() {$(".card").click()});
</script>

<style>
.outer {cursor: pointer}
</style>

With this example, you will correctly have two buttons rendered to screenreaders, the first one "The card itselfis the primary CTA" and the second one "Secondary CTA", while a mouse click on the whole card would lead the same action as the first button.

Upvotes: 3

Mechanic
Mechanic

Reputation: 5380

One important problem is related to event-capturing; if you click on a interactive element nested inside another interactive element(e.g. a select inside a clickable button) there would be an interference here and two case might happen depends on the browser;

case 1 is that both element will raise that event (e.g. click) event  

case 2 is that parent element will capture the event and the nested element won't raise that event

in fact both cases will result in non-deterministic behavior;

This is not limited to click events actually, but click event is more tangible; Also the screen reader will fail to parse the markup; keyboard-interaction won't work as expected; try it in the snippet below:

del.addEventListener('click', function(){
  console.log('deleting ...')
})

save.addEventListener('click', function(){
  console.log('saving ...')
})

sel.addEventListener('change', function(){
  console.log('changing to', sel.value)
})
<div id='del'>
delete 
<button id='save'> save 
<select id='sel'>
  <option>foo</option>
  <option>bar</option>
<select>
<input name='a' type='radio' />
<input name='a' type='radio' />
<input name='a' type='radio' />

</button>
</div>

Upvotes: 1

D. Pardal
D. Pardal

Reputation: 6597

It's hard to answer "why" questions, because there are many factors to be considered and ultimately the reason is that the specification specifies it, but I'll give it a try.

When this behavior was spec'ed, this design style was not very common. A link was normally either a single image or a small portion of text. Take a look at this link to an article from the year 2000:
]
Only the title and the image are interactive. The rest is simple text.

Even today, this is not so common. Take also look at the Microsoft 365 pricing page:

Note how the card itself is not interactive, only the things inside it. You can see the primary CTA "Buy now" in the form of a button and the secondary CTAs in the form of hyperlinks.


Now, about your example: Is that card really a button? It might be subjective, but for me that's not a button. A button normally appears with a color scheme contrasting with the surrounding page. I would make the card a <div> and the secondary CTA a <button>.

However it might be confusing to users, as the card doesn't seem much interactive to me. Consider adding cursor: pointer to the <div> (beyond all the things necessary for accessibility)`.


I noted you tagged . I think this is not a great idea for people using screen readers, and I think most screen readers would have problems interpreting a button inside a button (if the browser accepted that at all).

I would use the "Microsoft 365 pricing page approach" instead. It's simpler and works well with HTML.

Upvotes: 1

GrahamTheDev
GrahamTheDev

Reputation: 24875

The answer is actually quite simple in principle. When you click on the interactive element inside another interactive element which function should you trigger?

In your example if I click on Secondary CTA should it fire the function for secondary CTA or should it fire the function for the card?

The fiddle below should demonstrate the problem, tab into the first button and press enter, then tab into the CTA and press Enter.

Obviously you could work around this but I think it demonstates the point.

$('.card').on('click', function(){
    console.log("card");
});
$('.cta').on('click', function(){
    console.log("cta");
});
.cta {
  padding: 4px;
  background: #00a9a9;
  color: white;
  width: 80px;
  margin: auto;
  margin-top: 8px;
  margin-bottom: 8px;
}

.card {
  width: 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button" class="card">
  The card itself is the primary CTA.
  <br/>
  <br/>
  some important content to read through.
  some important content to read through.
  some important content to read through.
  <div class="cta" tabindex="0">
   Secondary CTA
  </div>
</button>

This principle then continues through to Screen Readers and other Augmentative and alternative communication (AAC) devices.

Should they take into account the parent element when describing the child element? Should they allow the use of Space to activate if you nest a checkbox within a <button>, should Enter then affect only the button or both?

Upvotes: 4

Related Questions