Why Your "Perfect" Button Is Actually a Bug Waiting to Happen

Why Your "Perfect" Button Is Actually a Bug Waiting to Happen

Jul 04, 2026 semantic-html web-development accessibility frontend best-practices

Every developer has been there. You're building a component library, and the design calls for a button. Not just any button—a special button. It needs custom styling, a unique hover effect, maybe a gradient that shifts on press. So you grab a <div>, add some CSS, and call it done.

It looks perfect in Figma. It works in your demo. Then a real user tries to tab through your interface, submits a form with their keyboard, or navigates your site with a screen reader—and everything falls apart.

The problem isn't your CSS. The problem is that you asked a <div> to do a job it was never designed for.

The Div That Wanted to Be a Button

Here's what typically happens: you need a button that looks a certain way. You create a <div>, add class="btn", and style it until it matches the design. It looks great. But now you need it to do button things.

So you add a click handler. Then a hover state. Then a focus ring for keyboard users. Then you realize Space and Enter don't work, so you add keydown listeners. The button needs to be disabled sometimes, so you add a .disabled class that hopefully blocks all the right interactions. Somewhere in there, you add role="button" and tabindex="0" to keep accessibility tools from completely ignoring your creation.

Congratulations. You've built a button-shaped object that requires roughly 200 lines of JavaScript to approximate what the browser would have given you for free.

This is what we mean when we talk about "paying rent" on bad architectural decisions. Each line of workaround code is interest on the original choice to use the wrong element.

The Invisible Contract

Here's the thing about native HTML elements: they come with an entire interaction contract that most developers never think about until it's missing.

When you use a real <button>, you get:

  • Automatic keyboard handling — Enter and Space work exactly as users expect
  • Proper focus management — It enters the tab order naturally, can be focused programmatically
  • Disabled state behavior — Blocks all interaction, becomes un-focusable, is announced as disabled
  • Form participation — Submits forms, respects form validation, participates in form.reset()
  • Accessibility semantics — Screen readers announce it as a button without additional ARIA
  • Platform conventions — Users already know how buttons behave because buttons behave consistently everywhere

With a <div> dressed as a button, you get... the CSS. Everything else is on you to implement. And here's the dirty secret: no matter how thorough you are, your implementation will be worse than what browsers provide. Browsers have teams of engineers optimizing these behaviors across platforms, edge cases, and assistive technologies. Your custom button has you.

The Real Question Isn't "Can I Style It?"

This is where design-engineering collaboration either shines or creates technical debt. Developers often interpret "semantic HTML" as "accepting the browser's ugly defaults." That's not what we're talking about at all.

The question isn't "can I make this look custom?" It absolutely can be custom. The question is: "which native behaviors should survive the styling?"

A <button> can have any visual treatment you want. You can remove all default styling with appearance: none. You can add gradients, shadows, animations, custom fonts—whatever the design calls for. The browser's interaction contract doesn't care what your button looks like.

What you can't do easily with a <div> is replicate what the browser gives you automatically. So choose your battles: spend your engineering effort on what makes your product unique, not on reinventing buttons.

Building for the Real Web

Your users don't use your product in a vacuum. They tab between fields while their hands are on the keyboard. They use voice control to navigate. They magnify the screen to 300% because that's what works for them. They have different browsers, different operating systems, different assistive technologies.

The web platform gives you a massive head start with semantic HTML. Native elements are accessible by default, keyboard-navigable by default, cross-platform by default. When you fight that infrastructure, you're not just making more work for yourself—you're creating components that will fail in subtle ways for real users.

Next time you're about to reach for a <div> with role="button", ask yourself: am I building something that genuinely needs a custom implementation, or am I just afraid of the <button> tag? Because the boring solution is usually the right one. And "the browser handles it correctly" is a feature, not a limitation.

The semantic web isn't about following rules. It's about using the tools that were built for the job—and spending your energy where it actually makes a difference.

Read in other languages:

EL CS UZ TR SV FI RO PT PL NB NL HU IT FR ES DE DA ZH-HANS