skip to content

A team ships a clickable control as `<div class="btn" onclick="save()">Save</div>`. What does a real `<button>` element give you that this div does not, and what would you have to add to make the div behave equivalently?

level: middleimportance: must knowfreq 82%

answer

  1. mouse-only control
  2. never enters the tab order
  3. Enter and Space do nothing
  4. role without behaviour is a lie
  5. one element replaces a dozen lines

basics

~20 s

A native button is focusable, fires on Enter and Space, reports a button role, and submits forms. The div has none of it — you must add a role, tabindex, key handlers, disabled handling and focus styling just to draw level.

solid answer

~50 s

The div is invisible to everyone who is not using a mouse. It is not in the tab order, so keyboard users never reach it; it has no role, so a screen reader announces plain text; Enter and Space do nothing; and it cannot participate in form submission. A real `<button>` gives all of that for free — implicit `button` role, focusability without `tabindex`, activation on Enter and Space that dispatches a genuine click, `type="submit"` by default inside a form, a working `disabled` attribute that also removes it from the tab order, and a UA focus indicator. To match it you would add `role="button"`, `tabindex="0"`, keydown/keyup handlers for Enter and Space with `preventDefault` on Space to stop page scroll, `aria-disabled` plus a guard in the handler, and your own focus-visible styling. The correct answer in an interview is: delete the div and use `<button type="button">`.

code

html · 6 lines
html
<form action="/save" method="post">
  <label for="note">Note</label>
  <input id="note" name="note">
  <!-- focusable, Enter/Space activate it, submits the form, disabled works -->
  <button type="submit" class="btn">Save</button>
</form>

go deeper

for a junior

Recall that a div is not focusable and has no keyboard activation, so a keyboard user can never reach or press it. Say that the fix is a real <button>, not more attributes.

for a middle

Enumerate the contract precisely — implicit role, tab order, Enter and Space activation, default submit type, working disabled — and describe the tabindex plus key-handler code you would have to write to imitate it.

for a senior

Frame it as ownership cost and risk: a hand-built button is platform behaviour you must retest on every browser and assistive technology, and a partial fix that announces a button nobody can press is worse than the unannotated div.

for a principal

Treat recurring div-buttons as a systems signal — a missing styled primitive, a CSS reset that punishes the native element, or a review process with no keyboard pass — and fix the cause rather than the instances.

## Why this is the canonical question Every interviewer has seen this markup in production. It is the cleanest test of whether a candidate understands that HTML elements carry *behaviour*, not just default styles — and whether they know what "accessible" means beyond "has an aria-label". ## What the div is missing, one contract at a time **Focusability.** Only certain elements are focusable by default: form controls, links with an `href`, and a handful of others. A `<div>` is not among them, so it never appears in the sequential tab order. A keyboard user pressing Tab passes straight over the control; it does not exist for them. **Keyboard activation.** Even after you make a div focusable, focus alone does nothing. `<button>` has activation behaviour defined by the platform: Enter and Space both dispatch a real `click` event, which is why a single click handler on a native button already works for the keyboard. A div's `onclick` fires only for pointer input. **Role.** The div has a generic role, so assistive technology announces the text "Save" as static content. There is no signal that it can be operated, and it is missing from the list of buttons a screen reader user can jump between. **Form participation.** A `<button>` inside a `<form>` defaults to `type="submit"` and submits the form with no script at all — which is also why you write `type="button"` explicitly when you do *not* want submission. A div can never do this; it is not a form control. **Disabled state.** The `disabled` attribute is only defined on form controls — `button`, `input`, `select`, `textarea`, `fieldset`, `optgroup`, `option`. On a native button it blocks activation, removes the element from the tab order, and exposes the disabled state to assistive technology. Writing `disabled` on a div does nothing whatsoever. **Focus indication and platform behaviour.** The UA draws a focus ring on native controls and matches `:focus-visible` heuristically; forced-colour modes recognise buttons; touch screen-reader gestures know how to activate them. All of that is keyed off the element being a real button. ## What the re-implementation actually costs To bring the div level you need, at minimum: ```html <div class="btn" role="button" tabindex="0" onclick="save()" onkeydown="if (event.key === 'Enter') { save(); } else if (event.key === ' ') { event.preventDefault(); }" onkeyup="if (event.key === ' ') { save(); }"> Save </div> ``` Note the asymmetry: by platform convention a button activates on Enter *down* and on Space *up*, and Space must be prevented on keydown or the page scrolls. Then you still need `aria-disabled="true"` plus an early return in `save()` for the disabled case (because `aria-disabled` reports the state without enforcing it, and the element stays focusable), a `cursor` and focus-visible style, and — if this control lives in a form — an explicit `requestSubmit()` call. That is roughly a dozen lines and three platform conventions to replace one element name. ## The bug that ships The failure is not theoretical. In a shipped app the div-button breaks: keyboard-only users (including people using switch access and voice control), screen-reader users who navigate by control type, and anyone whose pointer is unavailable. It also breaks the mundane cases — you cannot Tab to it to trigger it in a test, and it will not submit the form it lives in. A half-fix is worse than none: adding `role="button"` without `tabindex` and key handlers announces an operable control that cannot be operated, so the user hears a promise the markup does not keep. ## The answer to give Name the four things the div lacks — focus, keyboard activation, role, form participation — say what the reimplementation costs, then close with the real fix: `<button type="button" class="btn" onclick="save()">Save</button>`. Buttons are fully stylable; there is no design that justifies the div. If pressed on why teams do it anyway, the honest answer is inherited CSS resets and habit, not a technical constraint.

  • Why does the Space key need `preventDefault()` in a div-based button?
    Because Space is the page-scroll key by default. On a focused element with no native activation behaviour, pressing Space scrolls the document, so you must cancel the default on keydown. Native `<button>` already suppresses that for you — the browser knows Space means "activate this control", which is one more platform convention the re-implementation has to reproduce by hand.
  • Can you just add the `disabled` attribute to the div to disable it?
    No. `disabled` is defined only on form controls — button, input, select, textarea, fieldset, optgroup, option — and is ignored everywhere else. The substitute is `aria-disabled="true"`, which *reports* the state to assistive technology but enforces nothing: the element stays focusable and still fires handlers, so you also need an early return in the click and key handlers.
  • If the div-button sits inside a `<form>`, will pressing Enter submit the form?
    Not through the div — it is not a form control and has no submit behaviour. Enter may still submit the form via a different route if the form has a real submit button or a single text input, which makes the bug intermittent and confusing. If you keep the div, you have to call the form's `requestSubmit()` yourself from the handler.
  • Is there any case where a div with `role="button"` is acceptable?
    Rarely, and only when the element genuinely cannot be a `<button>` — for example when the interactive region must contain block-level content that a button's content model does not allow, or when a framework primitive forces the tag. Even then you owe the full contract: tabindex, both key handlers, disabled handling and focus styles, plus a test that exercises them.

saying these in an interview costs you the question

  • Says an aria-label alone makes the div accessible
  • Adds role="button" but no tabindex or key handlers
  • Thinks click handlers fire for keyboard users automatically
  • Claims native buttons cannot be styled to match designs
  • Puts the disabled attribute on a div and expects it to work

context