Skip to content

gr.Tabs: keyboard focus lands on invisible 1px visually-hidden duplicate tab buttons; narrow-viewport overflow button has no accessible name #13635

Description

@timebeing92

Describe the bug

gr.Tabs renders a duplicate, measurement-only copy of every tab button inside a .tab-container.visually-hidden wrapper. The wrapper has aria-hidden="true" but the duplicate buttons remain keyboard-focusable, so:

  1. The first Tab keypress on the page lands on an invisible 1px-high button. Every tab gets one duplicate (N tabs → N invisible stops). Because they sit inside aria-hidden="true", screen readers announce nothing while keyboard focus is parked on them — focus effectively disappears.
  2. At narrow viewport widths (e.g. 320px), the tab overflow menu button is an icon-only <button> with no accessible name (no text, no aria-label, no title), so assistive-technology users get an unnamed control.

Accessibility impact: WCAG 2.4.3 (focus order) and 4.1.2 (name, role, value); focusable content inside aria-hidden regions is also flagged by axe as a critical issue (aria-hidden-focus).

Suggested fix: add tabindex="-1" (or inert) to the visually-hidden measurement copies, and an aria-label (e.g. "More tabs") to the overflow button.

Have you searched existing issues? 🔎

Reproduction

import gradio as gr

with gr.Blocks() as demo:
    with gr.Tabs():
        with gr.Tab("First"):
            gr.Markdown("first tab content")
        with gr.Tab("Second"):
            gr.Markdown("second tab content")
        with gr.Tab("A Longer Third Tab"):
            gr.Markdown("third")
        with gr.Tab("Fourth Tab Name"):
            gr.Markdown("fourth")
        with gr.Tab("Fifth And Final"):
            gr.Markdown("fifth")

demo.launch()
  1. Open the app in Chromium at a 1280px-wide viewport, click the address bar, then press Tab once: document.activeElement is <button class="svelte-11gaq1">First</button> with a bounding rect height of 1px, inside .tab-container.visually-hidden[aria-hidden="true"]. Pressing Tab repeatedly walks through all five invisible duplicates before reaching the real tabs.
  2. Resize to 320px width: the overflow control appears as <button class="svelte-11gaq1"><svg …> with no text content, aria-label, or title.

Programmatic check used (Playwright, Chromium 1280×800):

[...document.querySelectorAll('a[href], button, input, select, textarea, [tabindex]')]
  .filter(el => el.tabIndex >= 0 && !el.disabled && el.offsetParent)
  .filter(el => el.getBoundingClientRect().height < 10)
// → 5 matches, all inside .tab-container.visually-hidden with an
//   aria-hidden="true" ancestor

Screenshot

Not applicable — the affected elements are invisible; the reproduction steps above show them programmatically.

Logs

No console errors; this is a DOM/accessibility-tree defect.

System Info

Gradio version: 6.20.0
gradio_client version: 2.5.0
Python: 3.12.7
OS: macOS 26.3.1 (arm64); also confirmed on Chromium via Playwright
Browser: Chromium (Playwright) and Chrome

Severity

I can work around it (replaced gr.Tabs with plain buttons), but it blocks accessible keyboard/screen-reader use of any app that keeps gr.Tabs.
\n\n## Before / after Spaces\n\n- Before fix\n- After fixdraft PR #13639

Before / after Spaces

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions