aria-labelledby
Identifies the element (or elements) that labels the current element. Creates an accessible name by referencing visible text content on the page.
Overview
The aria-labelledby attribute identifies the element(s) that label the current element. It provides the accessible name that screen readers announce.
Unlike aria-label which provides text directly, aria-labelledby references existing visible content, keeping the accessible name in sync with what sighted users see.
Multiple IDs can be space-separated to combine text from multiple elements. The order of IDs determines the order of the concatenated text.
Live Demo: Labelled Elements
Form Group Labelled by Heading
Billing Information
aria-labelledby="billing-section" → Group announced as "Billing Information, group"
Multiple Labels Combined
aria-labelledby="prefix-demo suffix-demo" → Announced as "Search products, search"
Dialog with Label
Delete Confirmation
Are you sure you want to delete this item?
aria-labelledby="dialog-demo-title" → Dialog announced as "Delete Confirmation, dialog"
aria-labelledby uses existing visible text for labels, ensuring screen reader users hear the same information sighted users see.
Code Examples
Basic Usage
<!-- aria-labelledby creates accessible name from referenced elements -->
<!-- Form field labelled by heading -->
<h2 id="billing-heading">Billing Address</h2>
<div role="group" aria-labelledby="billing-heading">
<label for="street">Street</label>
<input type="text" id="street" />
<label for="city">City</label>
<input type="text" id="city" />
</div>
<!-- Screen reader: "Billing Address, group" -->Multiple Labels
<!-- Combining multiple label sources -->
<!-- Label from multiple elements -->
<span id="name-prefix">Full</span>
<span id="name-label">Name</span>
<input
type="text"
aria-labelledby="name-prefix name-label"
/>
<!-- Announced as: "Full Name, edit text" -->
<!-- Label includes visible text + context -->
<table>
<tr>
<th id="date-header">Date</th>
<th id="time-header">Time</th>
</tr>
<tr>
<td>
<input
type="date"
aria-labelledby="date-header row1-label"
/>
</td>
<td>
<input
type="time"
aria-labelledby="time-header row1-label"
/>
</td>
<th id="row1-label">Meeting 1</th>
</tr>
</table>
<!-- First input: "Date Meeting 1, edit text" -->Dialog Modal
<!-- Dialog with aria-labelledby -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
>
<h2 id="dialog-title">Confirm Deletion</h2>
<p id="dialog-desc">
Are you sure you want to delete this item?
This action cannot be undone.
</p>
<button>Cancel</button>
<button>Delete</button>
</div>
<!-- Screen reader: "Confirm Deletion, dialog" -->Self-Referencing
<!-- Including the element's own text in label -->
<button
id="save-btn"
aria-labelledby="save-btn save-status"
>
Save
</button>
<span id="save-status" class="sr-only">
(changes pending)
</span>
<!-- Announced as: "Save (changes pending), button" -->
<!-- Combining visible label with hidden context -->
<a
href="/products/widget"
id="product-link"
aria-labelledby="product-link product-category"
>
Widget Pro
</a>
<span id="product-category" class="sr-only">
in Electronics category
</span>
<!-- Announced as: "Widget Pro in Electronics category, link" -->React Components
// React components with aria-labelledby
import { useId } from 'react';
// Form section with heading as label
function FormSection({ title, children }) {
const headingId = useId();
return (
<div
role="group"
aria-labelledby={headingId}
className="form-section"
>
<h3 id={headingId}>{title}</h3>
{children}
</div>
);
}
// Modal dialog with aria-labelledby
function Modal({ title, description, children, onClose }) {
const titleId = useId();
const descId = useId();
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
aria-describedby={description ? descId : undefined}
>
<h2 id={titleId}>{title}</h2>
{description && <p id={descId}>{description}</p>}
{children}
<button onClick={onClose}>Close</button>
</div>
);
}
// Table cell input labelled by headers
function DataCell({ rowLabel, columnLabel, value, onChange }) {
const rowId = useId();
const colId = useId();
return (
<>
<span id={rowId} className="sr-only">{rowLabel}</span>
<span id={colId} className="sr-only">{columnLabel}</span>
<input
type="text"
value={value}
onChange={onChange}
aria-labelledby={`${colId} ${rowId}`}
/>
</>
);
}
// Region with dynamic label
function ContentRegion({ labelPrefix, labelSuffix, children }) {
const prefixId = useId();
const suffixId = useId();
return (
<section
role="region"
aria-labelledby={`${prefixId} ${suffixId}`}
>
<span id={prefixId} className="sr-only">{labelPrefix}</span>
<span id={suffixId} className="sr-only">{labelSuffix}</span>
{children}
</section>
);
}
// Usage
function App() {
return (
<>
<FormSection title="Shipping Information">
<label>Address</label>
<input type="text" />
</FormSection>
<Modal
title="Delete Account"
description="This will permanently delete your account."
onClose={() => {}}
>
<button>Confirm Delete</button>
</Modal>
</>
);
}
