A screen reader user lands on your home page and hears: “link, link, link, button, image, IMG_4471 dot JPG, Read more, link, Read more, link.” The social icons in your header, the hamburger menu, the search magnifier and the cart icon all look clear on screen, yet none of them has a name a screen reader can speak. The photos are announced by file name, and ten identical “Read more” links give no hint where each one goes.
This happens because assistive technology does not see your design. It reads the accessibility tree, a structured version of the page the browser builds from your HTML, where every element has a role (link, button, image), a name (what it is called) and sometimes a state (expanded, pressed). If the name is missing or meaningless, so is the element. These are some of the most common failures in automated scans and accessibility complaints, and nearly all of them are quick to fix once you know where the name comes from.
What is happening
The browser computes an accessible name for every link, button, image and form control using a defined order of sources. In short: aria-labelledby wins first, then aria-label, then native sources such as the alt attribute of an image or the text content of a link or button, and finally the title attribute as a last resort. An icon font or a background image provides no name at all. An inline SVG provides one only if you give it one.
| Criterion |
Level |
What it requires |
| 1.1.1 Non-text Content |
A |
Every meaningful image, icon and graphic has a text alternative that serves the same purpose; decorative ones are hidden from assistive technology |
| 2.4.4 Link Purpose (In Context) |
A |
The purpose of each link can be understood from its text, or its text together with its programmatic context (same sentence, paragraph, list item or table cell) |
| 4.1.2 Name, Role, Value |
A |
Every interface component exposes its name and role, and any state such as expanded or pressed, to assistive technology |
| 2.5.3 Label in Name |
A |
When a control has a visible text label, its accessible name contains that visible text, ideally at the start |
The fixes below follow one principle: use native HTML first (a real img with alt, a real button with text), and reach for ARIA only when native HTML cannot express what you need.
Before you start
You need WordPress editor access, access to your theme or page builder header and footer templates, and a browser with developer tools. For screen reader testing you need either a Mac or iPhone (VoiceOver is built in) or a Windows PC with the free NVDA screen reader from nvaccess.org.
How to run the commands in this guide. Browser commands run in the developer tools Console tab (F12, or Command+Option+I on a Mac) with your site open; they only read the page. The database command runs over SSH in your site folder, the one that contains wp-config.php, using WP-CLI, which is called wp or wp-cli depending on your host. It only reads data, but take a backup before you make bulk changes based on its results.
Step 1: Find the nameless elements
Run axe DevTools or WAVE on your key templates. The rules to look for are image-alt (images with no alt attribute), button-name, link-name, svg-img-alt and role-img-alt. Then check the elements tools often miss, using the Console:
Array.from(document.querySelectorAll("a[href], button, [role=button]")).filter(function (el) {
var hasImgAlt = Array.from(el.querySelectorAll("img")).some(function (i) { return i.alt.trim(); });
return !el.textContent.trim() && !el.getAttribute("aria-label") && !el.getAttribute("aria-labelledby") && !hasImgAlt;
});
No placeholders. The code collects every link and button, then keeps those with no visible text, no aria-label, no aria-labelledby and no image with non-empty alt text inside. It is a quick approximation of the browser name calculation, not a replacement for it. Good output is an empty array, []. Bad output lists elements; hover each to highlight it on the page. Typical results are social icons, a cart icon, a search toggle, slider arrows and a hamburger button.
To see the exact name the browser computed for any element, inspect it and open the Accessibility pane (in Chrome and Edge, beside Styles; in Firefox, the Accessibility panel). The Computed Properties section shows Name, Role and the source the name came from. If Name is empty, a screen reader will announce only the role.
Step 2: Write alt text that does the job
Alt text is not a description of pixels. It is the text that replaces the image for someone who cannot see it. Ask what the image contributes on that page, then decide which of these cases applies:
| Image type |
What to write |
Example |
| Informative (a photo or diagram that adds information) |
A concise description of the information it conveys |
alt="Technician replacing a cracked water heater valve" |
| Decorative (adds mood only, or repeats nearby text) |
An empty alt attribute, so screen readers skip it |
alt="" |
| Functional (an image inside a link or button) |
The destination or action, not the picture |
alt="Riverside Plumbing home" on the header logo link |
| Image of text |
The same words as the text in the image |
alt="Spring sale: 20 percent off all services" |
| Complex (chart, map, infographic) |
A short alt naming it, plus the full information in nearby text or a data table |
alt="Bar chart of monthly bookings for this year, described below" |
Key rules. An empty alt="" is correct for decoration, but a missing alt attribute is a failure, because many screen readers then read the file name. Do not start with “image of” or “picture of”; the screen reader already announces it as an image. Keep it short, usually a sentence or less. Do not stuff keywords. And do not repeat a visible caption word for word in the alt text, or the visitor hears it twice.
For complex images, put the detail where everyone can reach it: a paragraph below the chart, a data table, or an expandable “Chart details” section. A figure with a figcaption keeps the image and its description together. Avoid images of text altogether where you can (WCAG 1.4.5 asks for real text when the same visual effect can be achieved with styled text), because real text scales, translates and stays sharp.
Step 3: Understand the WordPress media library fields
The Attachment Details panel in the media library shows several fields, and they do different jobs:
- Alternative Text: becomes the
alt attribute. This is the one screen readers use. WordPress stores it in the attachment metadata under the key _wp_attachment_image_alt.
- Caption: visible text shown below the image where the theme or block displays captions. It is for everyone, and does not replace alt text.
- Title: an internal label for the media library. It is not reliably exposed to assistive technology.
- Description: shown on the attachment page if your theme has one; not used as alt text.
There is a catch that confuses many site owners. When you insert an image with the core Image block, WordPress copies the alt text from the library into the block at that moment. If you later edit the alt text in the media library, images already placed in posts keep the old value. Update the block itself (select the image, then edit Alternative Text in the block settings sidebar). Featured images and many theme and builder components read the library value each time the page renders, so for those, editing the library is enough. When in doubt, view the page source after your change.
To build a review list of library images with no alt text, run this over SSH in your site folder:
wp db query "SELECT p.ID, p.post_title FROM $(wp db prefix)posts p LEFT JOIN $(wp db prefix)postmeta m ON m.post_id = p.ID AND m.meta_key = '_wp_attachment_image_alt' WHERE p.post_type = 'attachment' AND p.post_mime_type LIKE 'image/%' AND (m.meta_value IS NULL OR m.meta_value = '')"
There are no placeholders to replace. $(wp db prefix) runs a second WP-CLI command that prints your table prefix (often wp_, but many sites use a custom one), so the query hits the right tables. The LEFT JOIN keeps images even when they have no alt metadata row, and the final condition keeps only those whose alt is missing or empty. Good output is an empty result. Bad output is a table of IDs and titles. Not every result is an error: an image used only decoratively may correctly have no alt. Treat the list as a review queue, and open each image with /wp-admin/upload.php?item=ATTACHMENT_ID, replacing ATTACHMENT_ID with the ID from the first column.
Step 4: Fix icon-only buttons and links
Confirm the cause
An icon button usually looks like <button><i class="icon-search"></i></button> or <a href="https://facebook.com/PAGE"><svg>...</svg></a>. The Accessibility pane shows an empty Name. NVDA announces “button” and nothing else.
Two good fixes
<button type="button" aria-label="Search">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
<a href="SOCIAL_PROFILE_URL">
<svg aria-hidden="true" focusable="false">...</svg>
<span class="screen-reader-text">Our Facebook page</span>
</a>
SOCIAL_PROFILE_URL is the full address of your social profile. The first example names the button with aria-label. The second uses visually hidden text: screen-reader-text is the class most WordPress themes include for text that is read aloud but not shown. Both hide the icon itself with aria-hidden="true" so it is not announced separately. focusable="false" stops some older browsers from adding the SVG to the Tab order. Good result: the Accessibility pane shows Name “Search” and Role “button”.
Which should you use? Both are valid. Visually hidden text is real content, so it is more consistently picked up by automatic page translation, while some translation tools skip attribute values such as aria-label. aria-label is quicker to add in builders that offer an attribute field. In page builders, look for an accessible label, ARIA label or custom attributes option on icon, button and social icon elements; most current builders offer one.
Label in name (2.5.3)
When a control has visible text, do not override it with a different aria-label. A button showing “Search” with aria-label="Submit query" fails 2.5.3, and a voice control user who says “click Search” gets nothing. Keep the visible words in the name, at the start: aria-label="Search products" is fine for a button that shows “Search”.
Step 5: Fix “Read more” and other vague links
Screen reader users often pull up a list of all links on a page (NVDA+F7 in NVDA, the rotor in VoiceOver). Ten links that all say “Read more”, “Click here” or “Learn more” are useless in that list. WCAG 2.4.4 at Level A allows the surrounding sentence, paragraph or list item to supply context, so these do not always fail, but they are always worse than a clear link.
The best fix is to make the link text itself meaningful: link the post title, or write “Read the water heater guide”. Where the design requires a short visible label, add hidden context after it:
<a href="POST_URL">Read more<span class="screen-reader-text"> about POST_TITLE</span></a>
POST_URL and POST_TITLE are the address and title of the post the link points to; in a theme template they come from the_permalink() and the_title(). The visible words stay first, which keeps 2.5.3 happy, and the full name becomes “Read more about” plus the title. In block themes, the core Read More block already adds the post title as screen reader text, so prefer it over a hand-made button. In builder post grids, check the read more element for a setting that adds the title, or link the title and image instead and remove the duplicate button. Also give image links and title links in the same card a single link where possible, so keyboard users do not tab through three links to the same place.
Step 6: Fix buttons made of divs (4.1.2)
A div or span with a click handler looks like a button but is not one. It is not focusable, does not respond to Enter or Space, and is announced as plain text or not at all. Tab through the page: if you can click something with the mouse but cannot reach it with Tab, inspect it. A Role of “generic” in the Accessibility pane confirms it.
The fix is to change the element to <button type="button">. You get focus, keyboard activation and the button role for free. If a plugin generates the markup and you cannot change it, the fallback is role="button", tabindex="0" and script that activates it on Enter and Space, but that is more fragile and belongs in the plugin, so report it to the developer. Use links for navigation (they go somewhere) and buttons for actions (they do something on this page). Toggles also need state: aria-expanded for things that open and close, aria-pressed for on and off buttons.
Step 7: Make SVGs speak or stay silent
Inline SVG is invisible to screen readers unless you tell the browser what it is. Pick one of two treatments:
- Decorative, or inside a control that already has a name: add
aria-hidden="true" to the svg.
- Meaningful on its own (a standalone logo or an informative icon with no text next to it): add
role="img" and a name, either aria-label="DESCRIPTION" or a title element as the first child referenced by aria-labelledby. Replace DESCRIPTION with the same text you would write as alt text.
SVG files used through an img tag follow normal alt rules. SVGs used as CSS background images cannot have a name, so they must be decorative or paired with visible or hidden text.
Advanced: testing with NVDA and VoiceOver
Automated tools tell you a name exists. Only listening tells you it makes sense. Fifteen minutes with a screen reader on your main pages finds problems no scan will.
NVDA on Windows
Install NVDA from nvaccess.org. If you let the installer create a desktop shortcut, Control+Alt+N starts it. The NVDA key is Insert by default (Caps Lock can be enabled as an alternative). Use Chrome, Firefox or Edge. Useful keys in browse mode: H next heading, K next link, B next button, G next graphic, F next form field, D next landmark, add Shift to go backwards. NVDA+F7 opens the elements list (links, headings, landmarks). Control stops speech. NVDA+Space toggles between browse and focus mode. NVDA+Q quits.
VoiceOver on macOS
Press Command+F5 to toggle VoiceOver (or hold Command and triple-press Touch ID on supported Macs). The VoiceOver modifier, written VO, is Control+Option. Use Safari for the most reliable results. VO+Right Arrow and VO+Left Arrow move through items. VO+Space activates. VO+U opens the rotor, a menu of headings, links, form controls and landmarks; use the arrow keys to switch lists. VO+Command+H jumps to the next heading. Control stops speech.
VoiceOver on iPhone and iPad
Turn it on in Settings, Accessibility, VoiceOver, and set the Accessibility Shortcut so a triple-click of the side or Home button toggles it. Swipe right or left to move between items, double-tap anywhere to activate the selected item, and rotate two fingers on the screen to open the rotor, then swipe up or down to move by the chosen unit (headings, links, form controls).
What to listen for
Go through the header, main navigation, one content page and one form. Listen for: icons announced only as “button” or “link”; images read as file names or as long keyword lists; the same image announced twice (alt text plus identical caption); decorative flourishes announced at all; “Read more” with no context in the links list; controls that announce as “clickable” or as plain text instead of “button”; and menus that open without announcing “expanded”. Write down what you heard, word for word, next to what it should have been. That transcript is the most persuasive bug report you can give a developer.
When to contact your host
Names, roles and alt text live in your content, theme and plugins, so these fixes are yours or your developer. Contact your host if the database query above fails with a permissions or connection error, if WP-CLI is not available on your plan, or if you need a staging copy to test a bulk alt text change. Include the exact command, the full error text and the UTC time you ran it. For other server checks, see our diagnostic tools.
Not legal advice: this guide covers the technical fixes. If missing alt text or unlabeled buttons were cited in a complaint or demand letter, have a lawyer handle the legal side.