Skip to main content

Notes

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 has alt, adding aria-label doesn’t merge with those — it silently overrides them, per the precedence order above. That’s rarely intentional; it usually means an aria-label got 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-labelledby and reference it, instead of retyping the same string into aria-label.
  • Never set both on the same element — aria-labelledby silently wins, so a mismatched aria-label left over from a refactor just confuses the next developer reading the markup.
  • Custom aria-label alongside 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 into aria-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.

← Back to Notes