Strapi is the leading open-source headless CMS, used by agencies, in-house engineering teams, and product organizations who want full control over their content model and self-hosted infrastructure. That openness is its accessibility strength and weakness: every accessibility decision lives somewhere in the developer's content type, the rich-text editor configuration, the media library defaults, or the API response transformer — and any one of those layers can ship the wrong HTML to the front end. Strapi 5 introduced a Blocks rich-text editor that produces structured JSON (similar in shape to Sanity's Portable Text), which means rendering remains a renderer-level concern even in 2026. With the European Accessibility Act enforceable since 28 June 2025 against any business with EU customers and ADA demand letters increasingly targeting marketing sites and SaaS dashboards, Strapi-powered teams need a checklist that addresses both the headless backend and the JavaScript front end. This guide focuses on what a developer-and-editor team can change today: the Blocks editor configuration, the media-library alt-text field, content-type validation, the admin panel for editors, the public API responses that drive React or Astro front ends, and the deploy-time accessibility checks that catch regressions before they reach users. Each issue maps to the specific WCAG 2.1 success criterion it addresses so you can document remediation in your accessibility statement.

Common Accessibility Issues

critical

Media Library Allows Uploads Without Alternative Text

WCAG 1.1.1

Strapi's media library exposes an Alternative text field on every upload, but it is optional by default. Editors routinely upload images with no alt text, and because the public API still returns the image URL, the front end renders <img> elements with empty or missing alt attributes. Across a marketing site with hundreds of articles and product photos, this becomes a systemic WCAG 1.1.1 failure.

How to fix:

In the Strapi admin panel, navigate to Settings > Media Library and enable required validation on the alternative-text field. For Strapi 4 and 5 projects that need stricter enforcement, write a lifecycle hook on the upload plugin that blocks publishing of any media item with an empty alternativeText. Educate editors that decorative images should have explicit alt="" set rather than a missing alt attribute, and make sure the front end mirrors that in the rendered HTML.

Before
// src/index.js - no enforcement
module.exports = {
  register() {},
  bootstrap() {},
};
After
// src/index.js - require alt text on upload
module.exports = {
  register({strapi}) {
    strapi.db.lifecycles.subscribe({
      models: ['plugin::upload.file'],
      async beforeUpdate(event) {
        const {alternativeText} = event.params.data;
        if (typeof alternativeText !== 'string' || alternativeText.length === 0) {
          throw new Error('Alternative text is required for accessibility');
        }
      },
    });
  },
};
critical

Blocks Rich-Text Editor Renders as Generic Divs

WCAG 1.3.1

Strapi 5's Blocks editor stores content as structured JSON. Without a custom front-end renderer, many starter templates output every block as a <div> with class names, losing the semantic difference between <h1>, <h2>, <p>, <ul>, and <blockquote>. Screen-reader users cannot navigate by heading, and the page violates WCAG 1.3.1 because the document outline is not programmatically determinable.

How to fix:

Use @strapi/blocks-react-renderer (or the Vue/Svelte equivalent) and explicitly map every block type to its semantic HTML element. In Astro, install astro-strapi-blocks-renderer or write a small component that switches on block.type and emits <h2>, <h3>, <p>, <ul>, <ol>, <blockquote>, and <a>. Confirm that exactly one <h1> is used per page (typically rendered from the title field outside the Blocks content) so editors stay within heading-level conventions.

Before
// Default renderer outputs only divs
function Article({blocks}) {
  return <div>{blocks.map(b => <div key={b.id}>{b.text}</div>)}</div>
}
After
// Using @strapi/blocks-react-renderer
import {BlocksRenderer} from '@strapi/blocks-react-renderer'

function Article({blocks}) {
  return (
    <BlocksRenderer
      content={blocks}
      blocks={{
        heading: ({children, level}) => {
          const Tag = `h${level}`
          return <Tag>{children}</Tag>
        },
        paragraph: ({children}) => <p>{children}</p>,
        list: ({children, format}) => format === 'ordered' ? <ol>{children}</ol> : <ul>{children}</ul>,
        quote: ({children}) => <blockquote>{children}</blockquote>,
        link: ({children, url}) => <a href={url}>{children}</a>,
      }}
    />
  )
}
serious

Content-Type Builder Allows Heading Levels to Be Skipped

WCAG 1.3.1

Strapi's Blocks editor toolbar exposes h1 through h6 with no enforcement of order. Editors choose heading levels by visual weight rather than by document structure, producing articles that jump from h1 to h4 with no h2 or h3 between. Screen-reader users lose the ability to navigate the page, and the rendered HTML violates WCAG 1.3.1.

How to fix:

Disable h1 in the Blocks editor configuration so editors cannot create a second h1 (the page title field already provides it). Disable h5 and h6 as well unless the project genuinely needs that depth. Add an editorial guideline in the admin documentation. Optionally, add a publish-time validator that walks the Blocks JSON and rejects documents with heading-level skips.

serious

Public API Returns Sanitized HTML That Strips ARIA Attributes

WCAG 4.1.2

Strapi's default sanitization config (used to prevent XSS in user-generated content) often strips aria-* attributes, role attributes, and the <button> element's type attribute when sanitizer rules are not customized. This breaks WCAG 4.1.2 (Name, Role, Value) for any custom interactive components an editor adds to a content area.

How to fix:

Update the sanitize allowlist in config/middlewares.js to include aria-label, aria-labelledby, aria-describedby, aria-expanded, aria-controls, role, and the type attribute on <button>. Audit any custom plugin or controller that returns HTML to ensure it does not strip these attributes downstream. Test by adding a known accessible component (such as a disclosure button) to a page and verifying the rendered HTML carries the expected attributes.

Before
// config/middlewares.js - default sanitizer
module.exports = [
  'strapi::errors',
  'strapi::security',
  // ...
]
After
// config/middlewares.js
module.exports = [
  'strapi::errors',
  {
    name: 'strapi::security',
    config: {
      contentSecurityPolicy: { /* ... */ },
      sanitize: {
        allowedAttributes: {
          '*': ['aria-label', 'aria-labelledby', 'aria-describedby', 'aria-expanded', 'aria-controls', 'role'],
          button: ['type'],
          a: ['href', 'target', 'rel']
        }
      }
    }
  },
  // ...
]
serious

Admin Panel Login and Editor Are Not Keyboard-Friendly for Editors With Disabilities

WCAG 2.1.1

Editors with motor disabilities or who use voice-control software depend on full keyboard access to the Strapi admin panel itself. Some custom plugin pages, extension components, and integrations break tab order or trap focus. While Strapi core has improved keyboard support over time, plugins from the Marketplace are uneven, and a single bad plugin makes the editor unusable for someone relying on keyboard or voice input.

How to fix:

Audit each installed plugin's admin pages with the keyboard alone before approving for production. Disable plugins that trap focus or expose interactive controls only via mouse hover. File issues on the plugin repository (Strapi has an active maintainer community). For internal teams, document a list of approved plugins and require accessibility review for any new plugin before installation.

serious

Image Component Returns Multiple Sizes Without Aspect Ratio Metadata

WCAG 1.4.10

Strapi's Image component returns multiple format sizes (thumbnail, small, medium, large) with their pixel dimensions. Front-end frameworks that render these images often forget to set width and height attributes on the <img>, causing layout shift on slow networks and reflow problems at 400 percent zoom (WCAG 1.4.10 Reflow).

How to fix:

When rendering an image from Strapi in React, Vue, or Astro, always set the width and height attributes from the format metadata so the browser reserves space. Use the srcset attribute or the framework's native <Image> component to serve the correct size for the viewport, and pair it with responsive width/height values that preserve the aspect ratio.

serious

Form Submissions API Returns Errors Without Programmatic Association

WCAG 3.3.1

Strapi is commonly used to receive form submissions (contact, newsletter, lead capture) from a separate front end. The default REST API returns validation errors as a JSON object, but front ends often render those errors as a single banner without associating them with the offending input. Screen-reader users do not learn which field failed, and the page violates WCAG 3.3.1 (Error Identification).

How to fix:

Update the front-end form to map each error from the Strapi response to the corresponding input via aria-describedby. Set aria-invalid="true" on the input until the user corrects it. Render each error message inside a <div role="alert"> the first time it appears so screen readers announce it. Use the formApi error.details.errors array Strapi returns, which already includes a path that maps to your form field name.

Before
<form>
  <input name="email" />
  <button type="submit">Send</button>
</form>
{error && <p>{error.message}</p>}
After
<form>
  <label htmlFor="email">Email</label>
  <input
    id="email"
    name="email"
    aria-invalid={emailError ? 'true' : 'false'}
    aria-describedby={emailError ? 'email-error' : undefined}
  />
  {emailError && <div id="email-error" role="alert">{emailError}</div>}
  <button type="submit">Send</button>
</form>

Strapi-Specific Tips

  • Run accessibility checks at two layers: the front end (axe, pa11y, or Lighthouse) and the Strapi admin panel (axe DevTools against the running /admin URL) so editors with disabilities can rely on the editor as well.
  • Add @axe-core/cli as a dev dependency and run it against your front-end preview deploys on every pull request that changes a content type, the Blocks renderer, or a sanitization rule.
  • When migrating from Strapi 4 to 5, audit your renderer carefully — the Blocks editor's JSON shape changed and renderers that previously emitted the right HTML may now emit divs.
  • Document accessibility expectations in the README at the same level as security expectations. Treat alt-text validation, sanitizer allowlists, and renderer mappings as part of the platform contract.
  • If your Strapi instance powers multiple front ends (web, mobile app, kiosk), make accessibility tests part of every front-end CI pipeline since the API alone cannot enforce HTML correctness.

axe DevTools

Browser extension that scans both the Strapi admin panel and the rendered front end for WCAG violations, with code-level remediation guidance.

@strapi/blocks-react-renderer

Official renderer for Strapi 5's Blocks editor that supports custom mappings from JSON blocks to semantic HTML elements.

pa11y-ci

Command-line accessibility test runner that integrates with Strapi front-end CI pipelines and runs WCAG checks on every pull request.

Further Reading

Other CMS Checklists