Role="text" must not contain focusable elements
Last updated:
Who it helps:
Blind
Mobility
Standard:
WCAG 2.2 Level A
Related Guides
Elements using ARIA role=text must not include focusable descendants
Elements with ARIA role=text must not contain any child that can receive focus. This role is sometimes used to keep a phrase read as one piece when markup splits it, but it flattens semantics for everything inside. If a link, button, or other focusable control sits inside, assistive tech may treat it as plain text and fail to announce its role or state.
Why It Matters
- Screen reader users rely on announced name, role, and value. Wrapping interactive controls in role=text hides those semantics, so a link may be read as ordinary text.
- Keyboard users can tab to an element that provides no meaningful announcement, causing confusion and navigation errors.
- Cognitive load increases when interactive items are indistinguishable from plain text, making tasks like following links or submitting forms harder.
Common Causes
- Wrapping a phrase that includes a link or button in a span/div with role=text.
- Using role=text to fix line breaks or styling instead of CSS.
- Adding
tabindexto descendants inside a role=text container. - Applying role=text via a broad CSS/JS selector that unintentionally targets components.
- Placing
contenteditableor custom widgets inside role=text.
How to Fix
- Limit role=text to purely textual content. Do not include links, buttons, inputs, selects, textareas, iframes, summary, or any element with tabindex≥0 inside.
- If a phrase contains an interactive item, remove role=text from the wrapper and keep the interactive element separate so its semantics remain intact.
- To maintain continuous reading across visual markup (e.g., br, em, strong), wrap only the text nodes that need merging and exclude any interactive control from that wrapper.
- Remove
tabindexattributes from descendants within any role=text element. Make the focusable element a sibling instead. - Use CSS for presentation (spacing, line breaks) instead of role=text when possible.
- If you need a grouped announcement for a control, do not hide it inside role=text. Instead use
aria-label,aria-labelledby, or visually hidden text to provide the phrase while preserving the control’s role. - Recommendation: Add a lint/check step to flag
[role="text"]elements that contain focusable descendants in CI.
Focusable descendants to avoid inside role=text include:
a[href], button, input, select, textarea[tabindex]:not([tabindex="-1"]) and elements made focusable via scripting- summary, iframe, contenteditable=true
How to Test
Keyboard check:
- Tab through the page. Every stop should announce a clear role (link, button, etc.).
- If you land on an item inside a role=text wrapper and it is not announced with its role, that’s a failure.
Screen reader check (NVDA/JAWS/VoiceOver):
- Navigate by links and buttons. Ensure each control inside or near role=text is announced with correct role and label.
- Read the surrounding text by line/paragraph to confirm noninteractive text in role=text is read naturally.
Mobile/touch check (VoiceOver/TalkBack):
- Swipe through elements. Verify links and buttons near role=text are identified as interactive and can be activated.
DOM audit:
- Use a console snippet to find violations.
JS
const nodes = document.querySelectorAll('[role="text"]');
nodes.forEach(el => {
const focusable = el.querySelectorAll(
'a[href], button, input, select, textarea, iframe, summary, [contenteditable="true"], [tabindex]:not([tabindex="-1"])'
);
if (focusable.length) {
console.warn('Focusable descendant inside role=text:', el, focusable);
}
});Good Example
HTML
<h1>
<span role="text">New Products <br>Available Today</span>
</h1>
<p>
<span role="text">Learn more about our shipping options</span>
</p>
<p>
See our <a href="/help">help center</a> for details.
</p>Explanation: role=text is used only around plain text and a line break. The interactive link is separate, so its role is preserved.
Bad Example
HTML
<p>
<span role="text">Submit <button type="button">Send</button> request</span>
</p>Impact: The button is inside role=text, so some assistive technologies may announce it as plain text rather than a button.
Quick Checklist
- Do not place links, buttons, form fields, or any tabbable element inside role=text.
- Use role=text only for purely textual runs that need uninterrupted reading.
- Keep interactive elements as siblings of role=text wrappers, not descendants.
- Remove
tabindexfrom any child inside role=text. - Prefer CSS for layout/line breaks instead of role-based workarounds.
- Verify with keyboard and a screen reader that each interactive item announces its role and label.
- Add automated checks to flag
[role="text"]with focusable descendants.