ADA Compliance Professionals

    Role="text" must not contain focusable elements

    Last updated:

    Who it helps:
    Blind
    Mobility
    Standard:
    WCAG 2.2 Level A

    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 tabindex to descendants inside a role=text container.
    • Applying role=text via a broad CSS/JS selector that unintentionally targets components.
    • Placing contenteditable or 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 tabindex attributes 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 tabindex from 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.