aria-label Not Working on a Div or Button? Why, and How to Fix It
You add aria-label to an element, open a screen reader, and nothing happens. Or the label is read on one browser but not another, never appears on screen, or stays in English when the rest of the page is translated. These are among the most common accessibility questions, and almost all of them come from the same rule: aria-label only works on elements whose role allows a name, and it replaces the name rather than adding to it. This guide explains how browsers work out an accessible name, why aria-label is ignored on plain divs and spans, why it seems to fail on some buttons, what differs between NVDA, JAWS and Narrator on Windows, and how to localise labels properly.
What aria-label actually does
Every interactive element and many structural ones have an accessible name, the text a screen reader speaks and a voice control user says to activate it. Browsers work the name out in a fixed order and use the first source they find:
aria-labelledby, pointing at theidof other elements.aria-label, a text string on the element itself.- The native source: the
<label>of a form field, thealtof an image, the<legend>of a fieldset, the<caption>of a table. - The element's own text content, but only for roles that take their name from content, such as buttons, links, headings and table cells.
- The
titleattribute or a placeholder, as a last resort.
Two consequences explain most problems. First, aria-label replaces anything lower in the list, so a button with visible text Buy and aria-label="Add to basket" is announced as Add to basket. Second, aria-label is invisible. It never draws anything on screen and it is not a tooltip, which is why people ask why it is not showing.
<button> with visible text needs no aria-label at all.Why aria-label is ignored
| Where you put it | What happens | Why |
|---|---|---|
<div> or <span> with no role | Ignored by most screen readers, or read inconsistently | These have the generic role, and ARIA 1.2 prohibits naming generic elements |
<p>, <strong>, <em>, <code> | Ignored | Paragraph and text-level roles cannot be named either |
<a> with no href | Ignored, and the link is not focusable | Without href it is not a link; it has the generic role |
Element with role="presentation" or role="none" | Ignored | The role removes the element's meaning |
Inside aria-hidden="true" | Nothing is read | The whole subtree is removed from the accessibility tree |
Element that also has aria-labelledby | The aria-label is not used | aria-labelledby comes first in the name order |
| On a child span inside a button | Ignored; the button is named from its text | The span is generic, so its label does not count |
aria-label="" or a typo such as aria-lable | Ignored | An empty or unknown attribute is skipped |
ariaLabel in JSX | Not rendered as an attribute | React expects the hyphenated name aria-label |
Where it works reliably: buttons, links with an href, form fields, images and SVGs with role="img", landmarks such as <nav>, <section> and <form>, dialogs, and any element given a role that supports a name, such as button, group, region or img.
How to see what the browser is announcing
Do not guess from the source. Ask the browser what name it calculated.
- Chrome or Edge. Right-click the element, choose Inspect, then open the Accessibility pane next to Styles. Under Computed Properties, Name shows the final name and lists each source, with the ones that lost crossed out. Role shows whether the element is generic.
- Firefox. Right-click and choose Inspect Accessibility Properties. Elements that do not appear in the tree at all are being ignored.
To list every aria-label on a page and the role of its element, paste this into the console:
document.querySelectorAll('[aria-label]').forEach(el => {
const role = el.getAttribute('role') || el.tagName.toLowerCase();
const hidden = el.closest('[aria-hidden="true"]') ? ' (inside aria-hidden)' : '';
console.log(role, '|', el.getAttribute('aria-label') + hidden);
});
Anything listed as div, span, p or a without an href is a candidate for the fixes below. Then confirm with a real screen reader: NVDA is free on Windows, Narrator is built in (Ctrl+Windows+Enter), and VoiceOver is built into Mac and iPhone.
The fixes in code
A clickable div with aria-label
Swap it for a real button. You get the role, keyboard support and focus for free, and the label then works:
<!-- Before: not announced, not focusable -->
<div class="close" aria-label="Close" onclick="closeMenu()"><svg>...</svg></div>
<!-- After -->
<button type="button" class="close" aria-label="Close menu">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
A div that labels a group or area
If the div is a container you want named, give it a role that accepts a name, or use the right element:
<div role="group" aria-labelledby="ship-title">
<h3 id="ship-title">Delivery options</h3>
...
</div>
<section aria-label="Customer stories">...</section>
Extra context for a span or text
Text elements cannot be named, so put the extra words in the text itself and hide them visually:
<span class="price">£12<span class="visually-hidden"> per month</span></span>
.visually-hidden {
position: absolute; width: 1px; height: 1px; overflow: hidden;
clip-path: inset(50%); white-space: nowrap;
}
A button with visible text
Either drop the aria-label and let the text name the button, or make sure the label starts with the visible words. WCAG 2.5.3 Label in Name requires the accessible name to contain the visible text, so voice control users who say click Buy can still press it:
<!-- Fails 2.5.3 --> <button aria-label="Add to basket">Buy</button>
<!-- Passes --> <button aria-label="Buy Example Bakery sourdough loaf">Buy</button>
Screen reader differences on Windows
On Windows, three screen readers are common: NVDA, JAWS and the built-in Narrator. They read the same accessibility tree from the browser, but they choose differently what to say, which is why a label can seem to work on one and not another.
- Browse mode versus focus. When you Tab to a button or link, all three read its name. When you read the page with the arrow keys in browse mode, they read the content, and some skip names on elements that are not interactive or landmarks. An aria-label on a non-interactive container may only be read when you enter it, or not at all.
- Generic elements. Because naming a div or span is not allowed, screen readers are free to ignore it, and they do not behave the same way. Do not rely on a label that only one of them reads.
- Landmarks. A named
<nav>or<section>is read when you move into it and shown in the landmarks list. Two navs named the same are confusing, so give each a distinct label, such as Main and Footer. - Verbosity settings. Users can turn off some announcements. Test with default settings.
The fix for cross-reader differences is always the same: put the label on an element whose role supports it, and prefer visible text. If the name shows correctly in the DevTools Accessibility pane, the browser is doing its job, and a remaining difference is down to how a screen reader presents that role.
aria-label and translation by locale
An aria-label is a fixed string. It does not change by locale on its own: if your template says aria-label="Close", every visitor gets Close, whatever language the page is in. Automatic page translation tools may also skip or handle attribute text inconsistently, while they reliably translate visible text. So labels need to be translated by you, in the same way as the rest of your interface text.
- Use your translation system for the attribute. In Shopify themes:
aria-label="{{ 'accessibility.close' | t }}". In WordPress:aria-label="<?php echo esc_attr__( 'Close menu', 'mytheme' ); ?>". In React apps, pass the string through the same i18n function you use for visible text. - Prefer visible or visually hidden text. Text in the page, including visually hidden text, is translated with everything else, and
aria-labelledbypointing at visible text follows the translation too. - Set the page language. The
langattribute on the html element tells the screen reader which voice and pronunciation to use. A French page markedlang="en"is read with English pronunciation, which is a common reason labels sound wrong. See hreflang in Shopify for multi-language set-ups.
The fix, step by step
Step 1
Check the computed name in DevTools
Inspect the element in Chrome or Edge and open the Accessibility pane. Read the Role and the Name under Computed Properties. If the role is generic, the label is on an element that cannot be named. If the name comes from another source, such as aria-labelledby or the content, your aria-label lost the priority order. Note the result for each failing element before you change anything, so you can compare afterwards.
Step 2
Replace clickable divs and spans with buttons
Wherever a div, span or link without an href acts as a control, change it to a real button element with type set to button, or to a link with a real href if it navigates. Move the aria-label onto the new element and delete any tabindex and key handlers you added to fake one. Tab to it and confirm it receives focus, activates with Enter and Space, and the screen reader reads its name.
Step 3
Give named containers a proper role
For a container that genuinely needs a name, such as a group of related options or a page area, use a section, nav or form element, or add role set to group or region. Prefer aria-labelledby pointing at a visible heading, so sighted and screen reader users see the same words. Check the Accessibility pane now shows the role and the name, and that the area appears in the screen reader's landmarks list.
Step 4
Move extra context into visually hidden text
Where you used aria-label on a span, paragraph or price to add context, delete it and put the extra words inside the element in a visually hidden span instead. That text is part of the content, so every screen reader reads it and translation tools translate it. Add the visually hidden CSS class to your stylesheet once. Read the line with a screen reader and confirm it now includes the hidden words.
Step 5
Align button labels with visible text
For buttons and links that show text, either remove the aria-label so the visible text is the name, or rewrite it so it starts with exactly the visible words. This meets WCAG Label in Name and lets voice control users activate the control by saying what they see. For icon-only buttons, keep the aria-label and hide the icon with aria-hidden. Test by reading the computed name against the visible text.
Step 6
Remove conflicts that override the label
Search the templates for elements that have both aria-label and aria-labelledby, labels on children of buttons, empty aria-label values, misspellings and ariaLabel in JSX. Remove whichever source you do not want. Also check that no parent has aria-hidden set to true and no element has role set to presentation. Rerun the console snippet and confirm each remaining label sits on an element with a role that supports a name.
Step 7
Translate labels and set the page language
Pass every aria-label string through your translation system, the same as visible text: the t filter in Shopify Liquid, esc_attr__ in WordPress, or your app's i18n function. Make sure each language version has the correct lang attribute on the html element. Switch to a second language and check in the Accessibility pane that names are translated, then listen with a screen reader to confirm the correct voice is used.
Step 8
Test on NVDA, Narrator and VoiceOver
Tab through the fixed controls with NVDA and Narrator on Windows and VoiceOver on Mac or iPhone, then read the page with the arrow keys in browse mode. Each control should announce its role and name once. If the DevTools name is correct but one screen reader still differs, check you are not relying on a label on a non-interactive element. Finally, run the accessibility audit to catch any remaining unnamed controls.
Edge cases worth knowing
- Icons inside named buttons. Hide decorative SVGs and icon fonts with
aria-hidden="true", otherwise some screen readers read the icon too. See alt text for icons. - The title attribute as a stand-in. A title shows a tooltip on mouse hover only, is not shown to keyboard or touch users, and is read inconsistently. It is not a replacement for a visible label. See the title attribute.
- Form fields. An aria-label on an input overrides its
<label>. Keep the visible label and remove the aria-label, as covered in fixing form labels. - Repeated labels. Ten buttons all labelled More are just as unclear as visible text More. Include the item, such as More about Example Bakery wedding cakes.
- Dynamic changes. Changing an aria-label while focus is on the element is not always announced. For a toggle, use
aria-pressedoraria-expandedand keep the label stable.
Keeping your labels working
Add aria-labels in components, not page by page, so the right element and role are fixed once. Run the accessibility audit after theme or plugin updates, because a new version can change a button into a div. Keep the console snippet above in your test notes, and check any new icon-only control in the Accessibility pane before release. The wider rules for labels are in fixing ARIA labels, and invalid or misused attributes are covered in fixing ARIA errors.
1. Why is aria-label not working on my div?
A plain div has the generic role, and ARIA 1.2 does not allow generic elements to be named, so most screen readers ignore an aria-label on it. The same applies to span, p and a link without an href. If the div is a control, change it to a button element. If it is a container that needs a name, use section, nav or form, or add a role such as group or region. If you only need extra words read, put them in visually hidden text instead.
2. Why is aria-label not working on my button?
It usually is working, but in a way you did not expect, or it is on the wrong element. On a real button, aria-label replaces the visible text as the name, so the screen reader reads the label, not the text. If it is ignored, check whether it is on a span inside the button, whether aria-labelledby is also present, whether a parent has aria-hidden set to true, and whether the button is actually a div. The Chrome DevTools Accessibility pane shows which source won.
3. Why is my aria-label not showing on the page?
aria-label is never shown on screen. It only provides a name to assistive technology such as screen readers and voice control, and it does not create a tooltip on hover. If you want sighted users to see a label, use visible text or a proper tooltip component. The title attribute shows a hover tooltip, but not to keyboard or touch users, so it is a poor replacement. For icon buttons, many sites show a visible text label next to the icon as well as the accessible name.
4. Why does aria-label work in one screen reader but not another on Windows?
Different screen readers present the same accessibility tree in slightly different ways. NVDA, JAWS and Narrator all read names on buttons, links, form fields and landmarks reliably, but they vary when the label is on an element that should not be named, such as a div or span, or on a non-interactive container read in browse mode. Move the label onto an element whose role supports a name. If the name then looks right in DevTools, the remaining differences are usually just verbosity settings.
5. Does aria-label change by locale automatically?
No, aria-label is a fixed text string and does not change with the visitor's language. If your template hard-codes an English label, every language version of the page gets the English word. Automatic translation tools are also less dependable with attribute text than with visible text. Translate labels through your theme or app translation system, prefer visible or visually hidden text that is translated with the page, and set the correct lang attribute so screen readers use the right voice and pronunciation.
6. Should I use aria-label or aria-labelledby?
Use aria-labelledby when the name already exists as visible text on the page, such as a heading above a group of options, because it keeps the visible and spoken names the same and follows translations automatically. Use aria-label when there is no visible text to point to, such as an icon-only close button. If both are present, aria-labelledby wins and the aria-label is ignored. Where a native label element, alt attribute or visible button text can do the job, use those instead of either.
7. Does aria-label help SEO?
No, aria-label is not a ranking factor, and Google does not treat it as visible content. Its job is accessibility. Some search and AI crawlers may read attribute text, but you should never put keywords in aria-labels for that reason, because it makes screen reader output longer and confusing. Visible text, proper headings and descriptive link text help both people and search engines. A correct accessible name on icon buttons and links does improve accessibility audits, which matter for compliance and usability.
The accessibility audit lists controls with missing or ignored names, ARIA attributes on elements that do not support them and icon buttons with no label, so you can fix them at the template.
Open Accessibility Fixes โ