Docusaurus is the documentation framework Meta built and open-sourced, and it now powers documentation for tens of thousands of open-source projects and commercial SDK products including Algolia, Babel, Redux, and Jest. The default theme is React-based, supports MDX content, ships with Algolia DocSearch, and includes versioning, internationalization, and a built-in sidebar generator. While the maintainers have invested in accessibility, the framework's flexibility means real-world Docusaurus sites frequently regress on WCAG 2.1 AA: custom Markdown themes break code-block contrast, MDX components inserted by docs writers introduce heading hierarchy bugs, the version dropdown is rarely keyboard-tested, and the integrated search dialog often lacks a proper accessible name. With the European Accessibility Act enforceable since 28 June 2025 against any business that sells digital products into the EU and a growing pattern of plaintiff law firms targeting SDK and API documentation as part of broader product compliance complaints, engineering teams that ship docs alongside paid SaaS products are in scope. Docusaurus docs are also the primary developer-facing surface for many enterprise sales motions, which means an inaccessible docs site is a commercial risk independent of legal exposure. This checklist is written for the technical writer and the framework maintainer working together: writer-facing tasks they can complete inside MDX files, and template-level fixes the docs maintainer can ship in the swizzled theme components. Each issue cites the specific WCAG 2.1 success criterion that applies and includes the file path you would edit in a typical Docusaurus 3 project.

Common Accessibility Issues

critical

Default Code Block Theme (Palenight) Fails 4.5:1 Contrast on Comments and Strings

WCAG 1.4.3

Docusaurus 3 ships with the Palenight syntax-highlighting theme by default. Palenight uses muted purple comments (#697098) on a near-black background (#292d3e) that measure roughly 3.6:1, below the WCAG 1.4.3 minimum of 4.5:1 for normal text. Code is content in documentation, and developers regularly link directly to specific code examples, so unreadable code blocks block comprehension.

How to fix:

In docusaurus.config.js, change the prism theme to one with verified contrast. Replace require('prism-react-renderer').themes.palenight with require('prism-react-renderer').themes.vsDark or oneDark, both of which pass 4.5:1 across all token types. For light mode, set lightTheme to github or oneLight. Verify after the change with axe DevTools or a manual contrast check on a few code samples that include comments, strings, and keywords.

Before
// docusaurus.config.js
themeConfig: {
  prism: {
    theme: require('prism-react-renderer').themes.palenight,
    darkTheme: require('prism-react-renderer').themes.dracula,
  },
},
After
// docusaurus.config.js
themeConfig: {
  prism: {
    theme: require('prism-react-renderer').themes.github,
    darkTheme: require('prism-react-renderer').themes.vsDark,
  },
},
critical

Sidebar Categories Are Buttons That Trap Focus When Collapsed

WCAG 2.1.2

The default Docusaurus sidebar uses a button element to toggle categories open or closed. In some swizzled themes, the button receives focus on click but the focus is not released back to the sidebar after the user activates a child link, leaving keyboard users unable to tab back up to the toggle. The bug is amplified when the docs site uses the autoCollapseCategories option which closes other categories on toggle, sometimes resetting focus to the page top.

How to fix:

Test your sidebar with keyboard only: Tab into the sidebar, use Enter or Space to toggle a category, then Tab to a child link, activate it, and Tab back. Focus should land predictably. If you observe the trap, swizzle the DocSidebarItem component (run npm run swizzle @docusaurus/theme-classic DocSidebarItemCategory --eject) and audit the focus management. The fix is usually to remove the manual setFocus calls and let the browser's default tab order handle navigation.

serious

Algolia DocSearch Modal Has No aria-label or Initial Focus

WCAG 4.1.2

When Docusaurus integrates Algolia DocSearch (via the @docusaurus/preset-classic), the search button opens a modal dialog that should have role='dialog', aria-modal='true', and a programmatic accessible name pointing at the modal heading. Older DocSearch versions (pre-3.6) used role='combobox' but no aria-label, and screen-reader users heard only 'dialog' with no description. Initial focus also did not always land in the search input.

How to fix:

Upgrade @docsearch/react to version 3.6 or later (npm install @docsearch/react@latest) which fixes the dialog labeling and initial focus issues. If you cannot upgrade, swizzle the SearchBar component and add aria-label='Search documentation' to the trigger button and verify the modal sets focus on the input element on open. The DocSearch GitHub issue #2042 tracks this regression and contains workaround code if you are on an older Docusaurus 2 site.

serious

MDX Files Skip Heading Levels Because Writers Use ## as the Page Title

WCAG 1.3.1

Docusaurus auto-generates the page H1 from the front-matter title field. Writers familiar with markdown sometimes redundantly add a single # at the top of the body, producing two H1s on the rendered page. Conversely, writers who omit the front-matter title and start the body with ## end up with no H1 at all. Both fail WCAG 1.3.1 because the heading hierarchy is broken.

How to fix:

Establish a rule in your docs contribution guide: never use # headings in MDX body content. The front-matter title field is the only H1. Use ## for major sections, ### for subsections, #### for sub-subsections. Add a markdown lint rule with markdownlint-cli2 (specifically MD025 'multiple top-level headings' and MD002 'first heading should be top level') to enforce in CI. Configure remark-validate-headings as a Docusaurus remark plugin to catch this at build time.

Before
---
title: Authentication
---

# Authentication

This page covers the authentication API.

#### OAuth 2.0 flow

#### API keys
After
---
title: Authentication
---

This page covers the authentication API.

## OAuth 2.0 flow

## API keys
serious

Tabs Component Tab Order Does Not Match Visual Order

WCAG 2.4.3

Docusaurus's Tabs and TabItem components implement an ARIA tablist pattern, but in older versions (pre-3.4) the keyboard arrow-key handler did not always update tabindex correctly when authors used the queryString prop or default values. Users navigating with arrow keys could land on a tab visually two positions away from the focused indicator. WCAG 2.4.3 requires focus order to follow visual order.

How to fix:

Upgrade to Docusaurus 3.4 or later which fixes the tab order issue. Test every Tabs component on your docs site with keyboard only: focus the first tab, press right arrow, and confirm focus and visual indicator both move to the next tab. If you cannot upgrade, swizzle the Tabs component (npm run swizzle @docusaurus/theme-classic Tabs --eject) and replace the focus management logic with the latest upstream code from the Docusaurus repository main branch.

serious

Version Dropdown Has No Visible Focus Indicator in Default Theme

WCAG 2.4.7

The version dropdown in the navbar (used by docs sites that maintain multiple versions like API v1, v2) renders as a div role='button' with no native focus styling. When focused via keyboard, no outline appears, leaving keyboard users unable to see which control has focus.

How to fix:

Add focus-visible styles to your custom CSS at src/css/custom.css. The selector .navbar__link[role='button']:focus-visible should add an outline of at least 2px solid with 3:1 contrast against the navbar background. Test in light and dark mode separately. The rule should also apply to the language dropdown if your docs are internationalized.

Before
/* No focus styles defined */
.navbar__link {
  color: var(--ifm-navbar-link-color);
}
After
.navbar__link:focus-visible,
.navbar__link[role='button']:focus-visible {
  outline: 2px solid var(--ifm-color-primary);
  outline-offset: 2px;
}
moderate

Live Code Editor (react-live) Has No Accessible Name or Label

WCAG 1.3.1

Docs that use the @docusaurus/theme-live-codeblock plugin render an editable code area where readers can modify and re-execute code samples. The default theme renders this as a contenteditable div without label or aria-labelledby pointing at any heading. Screen-reader users encounter a focusable region with no description.

How to fix:

When you swizzle the Playground component (npm run swizzle @docusaurus/theme-live-codeblock Playground --eject), wrap the editor in a labelled container: <div role='region' aria-labelledby='playground-heading-{id}'> and place a visually-hidden h3 with id matching the aria-labelledby. Provide an explanatory paragraph above the editor that describes what the example demonstrates so screen-reader users can decide whether to engage with the live editor.

moderate

Admonition Boxes Use Color and Icons Without Text Labels

WCAG 1.4.1

Docusaurus admonition syntax (:::note, :::tip, :::warning, :::danger) renders boxes distinguished primarily by background color and an icon in the corner. The default theme does include the type as a heading inside each box (Note, Tip, Warning, Danger), but custom themes sometimes hide the heading via CSS to save vertical space, leaving only color and icon to convey the severity.

How to fix:

In your custom CSS, never hide the admonition heading. If your design requires a more compact look, reduce the font size and weight rather than display: none. Confirm by viewing the rendered admonition with a screen reader: it should announce 'Warning' or 'Danger' before reading the body text. The role='note' on info-style admonitions and the explicit text label provide redundancy with color and icon.

Docusaurus-Specific Tips

  • Docusaurus uses React Router under the hood, which means client-side route changes do not trigger a screen-reader page-change announcement by default. Add a custom client module that focuses the H1 on route change or announces the new page title via an aria-live region. The @docusaurus/plugin-debug source has examples.
  • When swizzling theme components, prefer the wrapper option over eject when possible. Wrappers receive future upstream accessibility fixes automatically, while ejected components are frozen at the version you swizzled.
  • Internationalized Docusaurus sites must set the lang attribute on the html element correctly per locale. Verify by switching locales and inspecting the rendered HTML. Misset lang attributes are a frequent WCAG 3.1.1 violation in i18n docs.
  • Run pa11y-ci or axe-core in CI on every pull request that touches MDX content. Algolia and Redux's Docusaurus repos both publish their CI configs publicly as a starting reference.
  • If you build a custom plugin that injects HTML into pages (banners, version-deprecation notices, license footers), audit the injection point for landmark conflicts. Inserting a second <main> element or nesting <nav> inside the article is a common bug.

axe-core React

React integration for the axe accessibility engine that runs WCAG checks on every render in development mode, surfacing violations directly in the browser console. Wire into your Docusaurus dev build with src/clientModules/axe-init.ts.

pa11y-ci

Command-line accessibility tester that runs WCAG checks against a list of URLs. Pair with the Docusaurus sitemap.xml output to test every published doc page on every deploy.

remark-validate-headings

Remark plugin that integrates into the Docusaurus MDX pipeline to catch heading hierarchy bugs at build time before they ship to production.

Further Reading

Other CMS Checklists