# axe-core buttons must have discernible text error - how to fix it

## TL;DR

Give every button an accessible name: visible text, an aria-label, or an aria-labelledby reference. Icon-only buttons are the usual culprit, so add an aria-label like "Close menu" and re-run axe. One line of why: screen readers announce buttons by their accessible name, and a button with no name is announced as just "button", which tells the user nothing.

## The error, verbatim

```text
{
  "id": "button-name",
  "impact": "critical",
  "help": "Buttons must have discernible text",
  "nodes": [
    { "target": [".icon-btn"], "failureSummary": "Fix any of the following: Element does not have text that is visible to screen readers" }
  ]
}
```

## Fix it step by step

### Step 1: Reproduce on one page

```bash
npx @axe-core/cli https://example.com --rules button-name --save axe-button.json
```

Expected: axe-button.json is created and its violations array contains an entry with id button-name and one or more nodes.

### Step 2: List the offending buttons

```bash
node -e "const r=require('./axe-button.json'); r.violations.forEach(function(v){v.nodes.forEach(function(n){console.log(n.target.join(' '))})})"
```

Expected: You see CSS selectors like .icon-btn pointing at each nameless button.

### Step 3: Find them in your source

```bash
rg -n --glob '*.{jsx,tsx,vue}' 'aria-label' src/components | head -40
```

Expected: A short list of components to inspect; the flagged selectors map to icon-only button elements.

### Step 4: Add an accessible name and re-scan

```bash
npx @axe-core/cli https://example.com --rules button-name
```

Expected: Exit code 0 and output shows 0 violations. In the browser, a screen reader now announces the label, for example Close menu, instead of just button.

### Step 5: Gate the rule in CI

```bash
npx @axe-core/cli https://example.com --exit | tail -3
```

Expected: Non-zero exit while any violation remains; add this command to CI so the fix never regresses.

## When to use this skill

- Your axe-core report lists this exact rule id under violations
- You are clearing automated WCAG 2.1 AA failures before a release or audit
- A CI a11y gate (pa11y-ci, lighthouse CI, cypress-axe) is red because of this rule

## When NOT to use this skill

- The issue only shows up in manual screen-reader testing and axe reports zero violations for the rule
- You are doing a full manual WCAG audit, this skill covers the single automated rule only
- The page is a third-party embed you cannot edit, flag it to the vendor instead

## Compatibility

axe-core 4.8+ (rule button-name, WCAG 2.4.6 / 4.1.2). Same fix pattern applies to axe-playwright, cypress-axe, and axe DevTools, which all run the same rule engine. Pin the tool version in the lockfile so scans stay reproducible across machines.

## Variant phrasings

### button-name violation: element does not have text visible to screen readers

Same rule, same fix. This is the failureSummary string axe prints per node.

### axe icon button no accessible name

Practitioner phrasing for the same problem: icon-only buttons missing aria-label.

### axe DevTools flags the same rule

The browser extension runs the same rule engine; fix once and it clears in every runner.

## Why it happens

A button's accessible name comes from its text content first, then aria-labelledby, then aria-label. Icon-only buttons built from an svg or icon font have none of those, so the computed name is empty and the rule fails. Component libraries love shipping icon buttons with no label prop, which is why this shows up on nearly every first scan. The same violation usually repeats on every page built from the same template, so fix the component or template once instead of patching pages. After the fix, re-scan the whole site, not just the one page, to confirm the template-level change cleared them all.

## Edge cases

- aria-label on a button that ALSO has visible text overrides the visible text for screen readers, keep them in sync or drop the aria-label.
- Buttons hidden with display none are ignored by the rule, but buttons hidden with opacity 0 still fail, make decorative ones truly hidden.
- If the icon is inside the button and also has its own accessible name, the svg name can leak into the button name, mark decorative icons aria-hidden true.
- Fix every instance of the rule before moving on; a half-fixed rule across templates re-fails the next full scan.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_kxHej_5NyJ-KhhRH6jdK-A
