# A button cannot be reached with Tab

Source: https://easeweb.dev/learn/native-buttons
Topics: Keyboard and focus > Custom controls work with Tab, Enter and Space (WCAG 2.1.1, 4.1.2); Buttons and links > Links go somewhere, buttons do something (WCAG 4.1.2)
The fix: Use a native button with a visible name and the correct type. Keep the existing visual design, add a visible focus state, and test the save result.
Test with: keyboard, screen reader, zoom, forced colors
References: https://www.w3.org/WAI/ARIA/apg/patterns/button/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A settings screen has a Save control styled like a button. Clicking it saves the form, but pressing Tab moves from the last field straight to the footer.

## Why it fails

The control is a div with a click handler. It has no native button role or keyboard activation.

## Who is affected

A keyboard user cannot complete the same action available to a pointer user. Voice commands may also lack a reliable control name.

## What automation and AI miss

A scanner may flag a clickable element, but a clean scan after adding `role="button"` does not show that Enter and Space work once, focus is visible, or the save result is communicated. Generated markup still needs interaction testing.

## Before: a div that only listens for clicks

```html
<div class="save-control" onclick="saveSettings()">Save changes</div>
```

A div is not in the tab order, has no role, and ignores Enter and Space. It looks like a button only to people who can see it and use a pointer.

## The patch that is not a fix

```html
<div class="save-control" role="button" tabindex="0" onclick="saveSettings()">Save changes</div>
```

This is the change scanners and code assistants often suggest. The control can now be focused and is announced as a button, but Enter and Space still do nothing. To finish the job by hand you would need key handlers, Space firing on key release, scroll prevention and a disabled state. A native button already does all of this.

## After: a native button

```html
<button type="button" class="save-control">Save changes</button>
<p id="save-status" role="status"></p>
```

```css
.save-control {
  font: inherit;
  /* Keep the existing padding, colors and radius here. */
}
.save-control:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
}
```

```js
const button = document.querySelector('.save-control');
button.addEventListener('click', async () => {
  await saveSettings();
  document.getElementById('save-status').textContent = 'Settings saved.';
});
```

A click handler on a button also runs for Enter and Space, so no key handling is needed. The status region must be in the page before its text changes, or the result may not be announced.

## Implementation decisions

A button inside a form submits by default. Use `type="button"` for a local action and `type="submit"` for submission. For a toggle, keep the label stable and expose `aria-pressed`. If the control takes the user to another page, use a link instead.

Keep the existing font, padding and shape on the native element. Browsers add their own button styles, so reset what conflicts with your design rather than going back to a div.

To stop the problem returning, make the native button the default in your component library. In code review, ask whether a control performs an action or navigates to another page: a button for an action, a link for navigation.

## Verify the fix

1. Reach the control with Tab and Shift + Tab.
2. Activate it separately with Enter and Space; each must run the action once.
3. Confirm a screen reader announces the visible name and the button role.
4. Check focus against the actual background, at increased zoom and in forced colors.
5. Confirm the save result is shown and announced.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared button component changes.

## Limits

This is a starting pattern, not a guarantee for every application. The snippets omit application-specific data handling. Test the complete journey with your target assistive technology.
