ARIA has three attributes for adding accessible names and descriptions to elements: aria-label, aria-labelledby, and aria-describedby. They're often used interchangeably, but they mean different things and screen readers present them differently.
aria-label
Provides an accessible name directly as a string. Use when there's no visible text to reference and you can't add visible text for design reasons.
<button aria-label="Close dialog">
<svg>...</svg>
</button>
The string replaces what the element would normally be called. Screen readers announce "Close dialog, button" instead of just "button".
aria-labelledby
References another element's text as the accessible name. Use when there is visible text that should serve as the label.
<h2 id="billing-heading">Billing address</h2>
<section aria-labelledby="billing-heading">
...
</section>
This is stronger than aria-label for two reasons: the label is visible (so sighted users and screen reader users get the same information), and it stays in sync automatically when the referenced element's text changes.
aria-describedby
Provides supplementary information — a description, not a name. Screen readers announce it after the element's name and role, often after a pause.
<input
id="email"
type="email"
aria-describedby="email-hint"
/>
<p id="email-hint">We'll never share your email with anyone.</p>
Use aria-describedby for hint text, error messages, and additional context. Use aria-labelledby or a <label> for the primary name.
The Priority Order
When multiple naming mechanisms are present, browsers follow a priority order: aria-labelledby > aria-label > native semantics (like <label>) > title attribute. Understanding this prevents surprises where your label is silently overridden.
The default should always be a visible <label> element with a for attribute. ARIA is for situations where that's not possible.