aria-label vs. aria-labelledby vs. aria-describedby: A Practical Guide for E-Commerce Sites
Quietramp ·
aria-label vs. aria-labelledby vs. aria-describedby: A Practical Guide for E-Commerce Sites
Three attributes, three different jobs, and one of the most common sources of confusion in ARIA
markup: aria-label, aria-labelledby, and aria-describedby. They sound like variations on the
same idea. They aren’t. Two of them compete to set an element’s name — and only one wins when
both are present — while the third does something entirely different: it adds a description that
never overrides the name at all.
Quick answer: aria-labelledby and aria-label both set an element’s accessible name, and
aria-labelledby wins if both are present on the same element. aria-describedby doesn’t touch the
name at all — it adds a separate, supplementary description that a screen reader announces
after the name. Mixing these up is how an icon-only “Add to cart” button ends up with no name, or
how a mislabeled aria-label silently overrides a button’s own visible text.
This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA does not resolve ADA or any other legal compliance question — consult qualified counsel for legal risk questions.
Name vs. description: the distinction that matters
Every interactive element a screen reader encounters needs an accessible name — the short string announced to identify it (“Add to wishlist,” “Quantity,” “Place order”). Some elements also carry an accessible description — longer, supplementary text read after the name, used for extra context that isn’t essential to identifying the control.
MDN’s aria-describedby reference
draws this line directly: “While aria-labelledby lists the ids of the labels or elements that
describe the essence of an object, aria-describedby lists the ids of the descriptions or
elements providing more information that the user might need… a label should be concise, while a
description is intended to provide more verbose information.” aria-label and aria-labelledby
are the two ways to set the name. aria-describedby is the one way to set the description.
They’re not three options for the same job — they’re two jobs, with three attributes split across
them.
Which one wins? The precedence order
Browsers compute an element’s accessible name by following a fixed algorithm, not by picking
whichever attribute happens to be present. The W3C Accessible Name and Description Computation
spec (§4.3.2) lays out the order: aria-labelledby with a
valid ID reference first, aria-label next, and only after both are absent does it fall back to
native host-language labeling — an HTML <label>, an alt attribute, a title attribute.
The practical consequence: if an element has both aria-labelledby and aria-label, the browser
uses aria-labelledby and silently ignores aria-label entirely. MDN’s aria-labelledby
reference
confirms it plainly: “If an element has both attributes set, aria-labelledby will be used.” The
same page adds that aria-labelledby “also takes precedence over most other methods of providing
an accessible name, such as <label>… and the element’s inner text.” A button’s visible text, a
paired <label>, an alt attribute — all of it gets overridden the moment a valid
aria-labelledby is present.
aria-label: a name with no visible text to point to
MDN’s aria-label reference defines it as a string “used to name an element, as long as the element’s role does not prohibit naming.” Use it when there’s no visible text on the page that could serve as the name — most often an icon-only button with no accompanying label.
<button type="button" aria-label="Add to wishlist">
<svg aria-hidden="true" focusable="false"><!-- heart icon --></svg>
</button>
Two caveats MDN calls out directly, both relevant to a storefront:
- Don’t stack it on an element that already has a native label. If an
<input>already has an associated<label>, or an<img>already hasalt, addingaria-labeldoesn’t merge with those — it silently overrides them, per the precedence order above. That’s rarely intentional; it usually means anaria-labelgot copy-pasted onto a field that was already labeled correctly. - It’s invisible text. Because it never renders on the page, a translation workflow, CMS update, or redesign can update the visible copy and miss the attribute — leaving a screen reader announcing stale or mismatched text indefinitely.
aria-labelledby: point to text that already exists
aria-labelledby takes a space-separated list of element IDs and concatenates their text content
into the accessible name — useful whenever the label text is already visible somewhere in the DOM,
which is the common case in e-commerce for a field with a heading or a legend sitting nearby rather
than a wrapped <label>.
MDN’s own worked example shows the concatenation behavior directly:
<h2 id="attr" class="article-title">13 ARIA attributes you need to know</h2>
<p>
There are over 50 ARIA states and properties, but 13 of them stand out…
<a href="13.html" id="rm13" aria-labelledby="rm13 attr">read more</a>
</p>
The link’s own text (“read more”) and the heading’s text join into a single accessible name:
“read more 13 ARIA attributes you need to know” — a specific name with no change to what’s visually
on the page. The same pattern applies to a quantity field labeled by a nearby “Quantity” heading
instead of a wrapped <label> (see Quietramp’s quantity stepper
article for the full spinbutton
pattern).
MDN’s guidance on choosing between the two is direct: “If there is no content that can be
referenced to create an accessible name, the aria-label attribute should be used instead” —
aria-labelledby when referenceable text exists, aria-label when it doesn’t.
aria-describedby: supplementary, never a substitute for the name
aria-describedby references one or more elements whose text becomes the accessible
description — read after the name, not instead of it, and it doesn’t need to be visible on the
page. A common e-commerce use: a shipping-cost note or a subscription’s fine print sitting near a
button, referenced rather than repeated in a tooltip.
<button type="submit" aria-describedby="ship-note">Place order</button>
<p id="ship-note">Orders over $50 ship free within 5–7 business days.</p>
The failure mode to avoid: treating aria-describedby as a way to label an unlabeled control.
It’s additive information, not a name. A button with only aria-describedby and no name from any
other source is still an unnamed button to a screen reader — it gets announced as “button,” followed
by whatever the description says, with no indication of what the button actually does.
The failure WCAG names directly: aria-label overriding visible text
WCAG SC 2.5.3 Label in Name
(Level A) exists because of exactly the precedence behavior described above. Its Intent: “the words
which visually label a component are also the words associated with the component
programmatically.” The Understanding doc names the specific risk of aria-label: it can “take
precedence in the name calculation, overriding the visible text as the accessible name even when
the visible text label is programmatically associated with the control.” And it names who this
breaks: “speech-input users who attempt to use the visible text label as a means of navigation or
selection (e.g., ‘move to Password’) will be unsuccessful” if the accessible name doesn’t contain
the words they can see and speak.
The concrete e-commerce version: a button reading “Add to Cart” on screen, built with
aria-label="cart-add-btn-1234" — an internal identifier left over from a component library. A
voice-control user says “click Add to Cart,” the command the button visibly asks for, and nothing
happens, because the computed accessible name doesn’t contain those words at all.
| Pattern | Visible text | aria-label |
Passes SC 2.5.3? |
|---|---|---|---|
| Icon-only, no visible text | (none) | "Add to wishlist" |
Yes — no visible text to conflict with |
| Icon + visible text, matching | “Add to Cart” | "Add to Cart" |
Yes |
| Icon + visible text, name doesn’t contain visible words | “Add to Cart” | "cart-add-btn-1234" |
No — fails 2.5.3 |
| Icon + visible text, label expands on it | “Add to Cart” | "Add Wireless Mouse to Cart" |
Yes — visible text is fully contained in the name |
The rule isn’t “never customize the name when visible text exists” — the last row shows a legitimate expansion is fine, since the Understanding doc’s own passing examples allow an accessible name that starts with or contains the visible label rather than matching it character for character. The rule is that the visible words have to survive somewhere in the computed name.
A quick checklist before shipping a component
- Icon-only, no visible text nearby? Use
aria-label, with a specific, action-describing string (“Remove item,” not “Remove”). - Visible text or a heading already sits next to the control? Use
aria-labelledbyand reference it, instead of retyping the same string intoaria-label. - Never set both on the same element —
aria-labelledbysilently wins, so a mismatchedaria-labelleft over from a refactor just confuses the next developer reading the markup. - Custom
aria-labelalongside visible text? Make sure it still contains those visible words — per SC 2.5.3, above. - Extra, non-essential context (fine print, shipping notes) belongs in
aria-describedby, not stuffed intoaria-label.
What a scanner catches, and what it can’t
Automated tools handle part of this well: axe-core and similar scanners reliably flag an
interactive element with no accessible name at all, and some flag aria-labelledby/aria-label
references pointing at IDs that don’t exist in the DOM. What they can’t judge is whether the name
is any good — a button with aria-label="button" or aria-label="cart-add-btn-1234" has a
technically valid, non-empty accessible name and won’t get flagged, even though it fails SC 2.5.3
and tells a screen reader user nothing useful. Confirming that a name actually matches what’s
visible on screen, and that it says something meaningful, is a manual read, not a rule a parser can
run.
FAQ
If I set both aria-label and aria-labelledby, which one does the screen reader actually
announce?
aria-labelledby. The browser checks for it first; aria-label is only a fallback when no valid
aria-labelledby reference exists.
Does aria-describedby ever override the accessible name?
No. It only contributes to the description, announced separately from — and after — the name. It
can’t make an unlabeled control labeled.
Can aria-describedby reference hidden text?
Yes — the referenced element doesn’t need to be visible for its text to count as a description,
which is why it works well for fine-print notes that shouldn’t clutter the visible page.
Does this affect SEO or GEO the same way visible text does? Indirectly. This attribute content isn’t rendered on the page, so it doesn’t carry the same on-page keyword signal as visible copy. Getting the name right is an accessibility question first — the SEO angle here is that “aria-label vs aria-labelledby” is itself a commonly searched developer question, not that the attributes are an SEO lever on their own.
Get your site’s ARIA usage checked by a person, not just a parser
An automated scanner confirms an accessible name exists. It doesn’t confirm the name matches what
a shopper sees, says something specific, or points at the right element. Quietramp’s $890 audits
include a manual pass through exactly this kind of naming mismatch — icon-only buttons, mislabeled
form fields, aria-label left over from a component refactor — the categories of failure a raw
scan reports as “passing.” See a real sample report or check
pricing — $890 one-time, $99/month for ongoing re-checks after fixes ship.
This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA does not resolve ADA or any other legal compliance question, and nothing here should be read as a compliance guarantee. Consult qualified counsel for legal risk questions.
This article was drafted with AI assistance and reviewed by a person for accuracy before publication.