Skip to content

fix(a11y): add skip-to-operations link, banner and main landmarks#10849

Open
bmatar wants to merge 1 commit intoswagger-api:masterfrom
bmatar:bug/7350-skip-link-landmarks
Open

fix(a11y): add skip-to-operations link, banner and main landmarks#10849
bmatar wants to merge 1 commit intoswagger-api:masterfrom
bmatar:bug/7350-skip-link-landmarks

Conversation

@bmatar
Copy link
Copy Markdown

@bmatar bmatar commented Apr 20, 2026

Description

Adds three structural a11y improvements:

  1. <main id="operations"> wraps the operations + webhooks + models region in BaseLayout. Provides a primary landmark for Screen Reader (SR) navigation in every swagger-ui consumer (standalone or custom layout). tabIndex="-1" + outline:none keeps it focusable programmatically (so the skip link can move focus to it) without adding a tab stop.

  2. <header role="banner"> wraps the Topbar in StandaloneLayout. SR users can jump to it via banner navigation.

  3. Skip-to-operations link in StandaloneLayout — visually hidden until focused, then appears top-left as a high-contrast button. The click handler scopes the target lookup via closest('.swagger-ui') so the link works correctly when multiple swagger-ui instances are mounted on one page.

Motivation and Context

The default layout currently has no <main> landmark. Standalone has no banner and no bypass mechanism. Keyboard and SR users must Tab through the topbar's logo, filter input, and dark-mode toggle on every navigation before reaching the operations list — and have no landmark structure to navigate via SR shortcut keys.

Refs #7350 (umbrella accessibility issue). WCAG 2.4.1 (Bypass Blocks), 1.3.1 (Info and Relationships).

Detected by Tactual.

How Has This Been Tested?

  • npm run test:unit — all suites pass

  • npm run build — clean

  • DOM probe (master vs this branch):

    BEFORE (master)
      <main>          : not present
      <header banner> : not present
      skip link       : not present
    
    AFTER (this branch)
      <main id="operations" tabindex="-1">   : wraps operations/webhooks/models
      <header role="banner">                 : wraps Topbar (StandaloneLayout only)
      <a class="swagger-ui__skip-link" href="#operations">Skip to operations</a>
                                             : prepended in StandaloneLayout
    
  • Keyboard flow on the local build:

    • First Tab → focus lands on .swagger-ui__skip-link (visible top-left)
    • Enter → focus moves to <main id="operations">
  • Manually verified with NVDA + Chrome on Windows 11:

    • Browse-mode landmark navigation exposes the topbar as a banner landmark (initially NVDA may announce nearby interactive context as "clickable banner landmark ..."; returning via landmark navigation announces "banner")
    • First Tab: skip link visible, announced by name
    • Enter on skip link: focus moves to <main>; NVDA announces the main landmark and then the first operation heading/link
    • Landmark quick navigation moves between the banner and main landmarks

Screenshots

The skip link is visually hidden until focused. After pressing Tab once on page load:

BEFORE (master) AFTER (this branch)
skip-link-before skip-link-after

No other visual change.

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

Adds three structural a11y improvements:

1. <main id="operations"> wraps the operations + webhooks + models
   region in BaseLayout. Provides a primary landmark for SR navigation
   in every swagger-ui consumer (standalone or custom layout).
   tabIndex="-1" + outline:none keeps it focusable programmatically
   without adding a tab stop.

2. <header role="banner"> wraps the Topbar in StandaloneLayout. SR
   users can jump to it via banner navigation.

3. Skip-to-operations link in StandaloneLayout — visually hidden
   until focused, then appears top-left as a high-contrast button.
   The click handler scopes the target lookup via closest('.swagger-ui')
   so the link works correctly when multiple swagger-ui instances are
   mounted on one page.

Manually verified with NVDA + Chrome on Windows 11.

Refs swagger-api#7350. WCAG 2.4.1 (Bypass Blocks), 1.3.1 (Info and Relationships).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant