The Button vs. Div Debate: Why Semantic HTML Matters for Accessibility

Web Development

Debunking the myth that `<div>` can replace `<button>`, this article highlights critical accessibility, focusability, and keyboard interaction issues, advocating for semantic HTML best practices.

A recurring and often perplexing debate among developers, particularly those enthusiastic about modern frameworks, revolves around whether a <div> element can truly be considered "just as good" as a <button> element. The straightforward answer is: no, it's not. Let's explore why.

The Fundamental Problem

Within the React community, and also among those who appreciate HTMX, a common pattern observed is the use of <div> elements configured for interaction:

<div onclick="showSignIn()">
  Open Modal
</div>
function showSignIn() {
  // Code to show the sign-in modal.
  // The details of what happens here vary by stack.
}

Why is this problematic?

  • Lack of Accessibility Semantics: This element does not announce itself as an interactive component to screen reader users.
  • Keyboard Inaccessibility: You cannot naturally focus on a <div> with a keyboard.
  • Limited Event Triggering: The onclick event only fires on an actual mouse click, not when Enter or Spacebar keys are pressed (a critical accessibility feature for keyboard users).

This pattern is alarmingly common in various codebases and demos. I've even encountered spirited arguments with prominent thought leaders in the React community who mistakenly assert that a <div> is "more accessible" than a <button>, citing examples like Twitter's implementation as justification.

That's not how it works

This reasoning is fundamentally flawed.

The Illusion of "Fixes"

Many HTML elements have implicit roles that inform assistive technologies, such as screen readers, about their function. The <button> element is a prime example; it has an implicit role of button, signaling to screen reader users that it's interactive and will trigger an action within the application.

The HTML role attribute can be used to explicitly add or modify an element's role. Proponents might argue (and I'm paraphrasing here):

"That attribute exists for a reason. You can add role="button" to a div to give it the correct semantics."

While adding role="button" addresses the semantic issue for some assistive technologies, it's merely a partial solution. That role does not affect the element's focusability or inherent keyboard behavior. Visually impaired users and those who navigate with a keyboard still cannot properly interact with it.

"No worries!" they might exclaim. "We can fix that, too!"

"You can make the element focusable with the tabindex attribute."

<div onclick="showSignIn()" tabindex="0">
  Open Modal
</div>

However, this is strongly discouraged! Manipulating focus order manually is fraught with peril. It's incredibly easy to misconfigure tabindex values, leading to a frustrating and unpredictable navigation experience for users, where focus jumps erratically across the page.

Ian Malcolm Jurassic Park

And even then, you still lack intrinsic keyboard interactivity.

But don't despair! You can theoretically add keyboard interactivity too. This requires listening for all keydown events, then filtering them by event.key to ensure your code only executes when the Enter or Spacebar keys are pressed (note: 'Spacebar' means checking for a literal space character: ' '). Crucially, this event listener cannot simply be attached to your div. You'd need to attach it to the document and then determine which element currently has focus.

document.addEventListener('keydown', (event) => {
  // Only run on Enter and Spacebar presses
  if (event.key !== 'Enter' && event.key !== ' ') return;

  // Make sure the element you care about has focus
  const notRealBtn = document.activeElement.closest('[onclick]');
  if (!notRealBtn) return;

  // Run your code, somehow...
});

While, technically, these are 'fixes,' the implications are significant:

You've Just Recreated Browser Functionality

Seriously, you've just meticulously recreated all the inherent functionality a <button> provides effortlessly. The question is, why would you undertake such an arduous task?

Consider the complexity of this HTML and JavaScript:

<div onclick="showSignIn()" tabindex="0">
  Open Modal
</div>

When you could simply write this semantic HTML instead:

<button onclick="showSignIn()">
  Open Modal
</button>

In contrast, a native <button> element inherently:

  • Has the correct role implicitly defined.
  • Is automatically focusable via keyboard navigation.
  • Fires a click event in response to both Enter and Spacebar presses when it has focus.

As developers, we often strive for efficiency. If you appreciate frameworks like React, you likely value writing less code. This is a commendable trait; after all, the best code is often the code you don't have to write.

Embrace true efficiency: utilize the correct semantic HTML element for its intended purpose, thereby avoiding a cascade of unnecessary and complex code!