Gatsby Accessibility Checklist 2026 | WCAG 2.1 AA & EAA Compliance
Last updated: 2026-03-24
Gatsby is a React-based static site generator that produces fast, pre-rendered websites from various data sources including Markdown, CMSes, and APIs. While Gatsby's server-side rendering and static HTML output provide a solid foundation for accessibility, the client-side hydration and React-based routing introduce significant accessibility challenges that developers must address deliberately. Gatsby's reliance on client-side navigation means that page transitions do not trigger the browser's native page-load announcements, leaving screen reader users unaware that new content has loaded. Additionally, Gatsby's rich plugin ecosystem means that third-party components may introduce accessibility barriers that are not immediately obvious. The gatsby-plugin-image component, while excellent for performance, requires careful configuration to ensure alt text is always provided and meaningful. As the European Accessibility Act enforcement ramps up across EU member states, Gatsby site owners must pay attention to focus management during route changes, semantic HTML in React components, keyboard operability of interactive elements, and proper ARIA usage throughout their component tree. This checklist covers the most common accessibility issues found in Gatsby sites and provides concrete fixes that work within Gatsby's React and GraphQL architecture.
Common Accessibility Issues
Gatsby uses @reach/router for client-side navigation, which means clicking a link does not trigger a full page reload. Screen readers rely on page-load events to announce new page content. Without explicit route change handling, users who rely on screen readers will click a navigation link and hear nothing, having no indication that the page content has changed or what the new page contains.
Install and configure @reach/router's built-in route announcement feature, or use gatsby-plugin-accessible-routing which adds an ARIA live region that announces page titles on route changes. Alternatively, add a custom RouteAnnouncer component in gatsby-browser.js that listens to route updates and announces the new page title via an aria-live region.
// gatsby-browser.js
// No route change handling - screen readers get no feedback
export const onRouteUpdate = ({ location }) => {
// only analytics tracking
trackPageView(location.pathname)
} // gatsby-browser.js
export const onRouteUpdate = ({ location }) => {
const pageTitleElement = document.querySelector('h1')
const pageTitle = pageTitleElement
? pageTitleElement.textContent
: document.title
// Announce route change to screen readers
const announcer = document.getElementById('route-announcer')
if (announcer) {
announcer.textContent = `Navigated to ${pageTitle}`
}
// Move focus to main content
const mainContent = document.querySelector('main')
if (mainContent) {
mainContent.setAttribute('tabindex', '-1')
mainContent.focus()
}
} Gatsby's StaticImage and GatsbyImage components generate optimized responsive images, but developers frequently omit the alt prop or pass empty strings for informative images. Because these components handle the underlying img element internally, missing alt text is less visible during development than it would be with plain HTML img tags.
Always provide a descriptive alt prop on StaticImage and GatsbyImage components. If the image is decorative, explicitly set alt to an empty string. In GraphQL queries that source images from a CMS, include the alt text field and pass it through to the image component. Add an ESLint rule (jsx-a11y/alt-text) to catch missing alt props during development.
<StaticImage
src="../images/team-photo.jpg"
layout="constrained"
width={800}
/>
<GatsbyImage
image={data.heroImage.childImageSharp.gatsbyImageData}
/> <StaticImage
src="../images/team-photo.jpg"
alt="Our accessibility team reviewing test results in the office"
layout="constrained"
width={800}
/>
<GatsbyImage
image={data.heroImage.childImageSharp.gatsbyImageData}
alt={data.heroImage.altText}
/> React's component-based architecture encourages breaking pages into small reusable components, which often results in excessive use of div elements as wrappers. Gatsby pages built with deeply nested div structures lack the semantic landmarks and heading hierarchy that screen reader users depend on to navigate and understand page structure.
Replace generic div wrappers with semantic HTML5 elements: header, nav, main, aside, footer, section, and article. Ensure each page has exactly one main element and a logical heading hierarchy starting with a single h1. Use the as prop available in many Gatsby-compatible UI libraries to render semantic elements.
<div className="layout">
<div className="header">
<div className="nav">...</div>
</div>
<div className="content">
<div className="title">Page Title</div>
<div className="body">Content here</div>
</div>
<div className="footer">...</div>
</div> <div className="layout">
<header>
<nav aria-label="Primary navigation">...</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<div className="body">Content here</div>
</main>
<footer>...</footer>
</div> Gatsby's extensive plugin ecosystem includes components for forms, carousels, modals, and other interactive patterns that may not follow accessibility best practices. Plugins like gatsby-plugin-google-analytics inject script-based content, while UI plugins may render components without proper ARIA attributes or keyboard support.
Audit all third-party Gatsby plugins that render visible UI components. Test each with keyboard navigation and a screen reader. For form plugins, verify that all inputs have associated labels and error messages are announced. For modal or overlay plugins, verify focus trapping and Escape key handling. Consider replacing inaccessible plugins with accessible alternatives or building custom components.
// Using a plugin that renders an inaccessible modal
import { PluginModal } from 'gatsby-plugin-fancy-modal'
<PluginModal trigger={<span>Open</span>}>
<div>Modal content with no focus management</div>
</PluginModal> // Custom accessible modal component
import { Dialog } from '@reach/dialog'
import '@reach/dialog/styles.css'
<button onClick={() => setShowModal(true)}>
Open details
</button>
<Dialog
isOpen={showModal}
onDismiss={() => setShowModal(false)}
aria-label="Feature details">
<p>Modal content with proper focus trapping</p>
<button onClick={() => setShowModal(false)}>Close</button>
</Dialog> Gatsby sites that load additional content dynamically, such as paginated blog lists, filtered product grids, or lazy-loaded sections, often fail to manage keyboard focus after the DOM updates. Users who rely on keyboard navigation may find their focus position lost or reset to the top of the page after content changes.
After dynamically updating content, move focus to the first new item or to a summary heading that describes the updated content. Use React refs and the useEffect hook to manage focus timing, ensuring the DOM has updated before the focus call. For paginated lists, focus the container or a heading like 'Page 2 of 5 results'.
const loadMore = async () => {
const newItems = await fetchMoreItems()
setItems([...items, ...newItems])
// Focus stays wherever it was or gets lost
} const resultsRef = useRef(null)
const loadMore = async () => {
const newItems = await fetchMoreItems()
setItems([...items, ...newItems])
}
useEffect(() => {
if (resultsRef.current) {
resultsRef.current.setAttribute('tabindex', '-1')
resultsRef.current.focus()
}
}, [items])
<h2 ref={resultsRef}>Showing {items.length} results</h2> Gatsby-Specific Tips
- Use gatsby-plugin-accessible-routing or a custom RouteAnnouncer in gatsby-browser.js to ensure screen readers announce page changes during client-side navigation. Test with NVDA or VoiceOver to verify announcements occur on every route change.
- Configure the jsx-a11y ESLint plugin in your Gatsby project's .eslintrc to catch accessibility issues during development. Gatsby's default ESLint config does not include accessibility rules, so you must add eslint-plugin-jsx-a11y explicitly.
- When using gatsby-plugin-image, always source alt text from your CMS or Markdown frontmatter through GraphQL queries rather than hardcoding it. This ensures content editors can update alt text without code changes.
- Test your Gatsby site both with JavaScript enabled (hydrated React) and with JavaScript disabled (static HTML) to ensure that the server-rendered output is accessible on its own, as some users and search engines will see the non-hydrated version.
Recommended Tools
eslint-plugin-jsx-a11y
An ESLint plugin that provides static analysis of JSX elements for accessibility violations, catching issues like missing alt text, invalid ARIA attributes, and non-interactive element event handlers during development.
axe DevTools
A browser extension for runtime accessibility testing of your Gatsby site's rendered output, identifying WCAG violations in the hydrated React components and providing specific code-level fixes.
@axe-core/react
A development-time accessibility auditing tool that logs WCAG violations directly to the browser console as you navigate your Gatsby site during development, catching issues before they reach production.
Further Reading
Other CMS Checklists
Get our free accessibility toolkit
We're building a simple accessibility checker for non-developers. Join the waitlist for early access and a free EAA compliance checklist.
No spam. Unsubscribe anytime.