Prismic is a headless CMS built around Slices - reusable, component-shaped sections that content editors arrange into pages using the Slice Machine workflow and a visual page builder. Like every headless platform, Prismic delivers structured content over an API and takes no position on how that content becomes HTML, which means accessibility on a Prismic site is decided in two places at once: in how the Slice models and fields are designed, and in how your front-end (Next.js, Nuxt, SvelteKit, Astro, or a native app) renders them. A perfectly coded React component can still fail WCAG because the image field has no required alt text, and a flawless content model can still fail because the Rich Text serializer was customized to output a div where a heading belongs. Prismic concentrates this risk in two specific places that do not exist on a traditional CMS: the Slice library, where one inaccessible Slice repeats across dozens of pages, and the Rich Text serializer, a single function that maps Prismic's structured text nodes to HTML elements and silently controls heading semantics, list markup, and link output for the whole site. With the European Accessibility Act now enforceable for businesses serving EU consumers, and ADA Title III and Section 508 obligations applying across Prismic's large base of marketing and product sites in the United States, teams need to treat both the model and the front-end as in scope. This checklist covers the issues we see most often on Prismic sites and the specific Slice settings, serializer code, and rendering targets that fix each one. None of this is legal advice; consult a qualified attorney for your jurisdiction.

Common Accessibility Issues

critical

Image Fields Rendered Without Required, Editor-Supplied Alt Text

WCAG 1.1.1

Prismic's Image field has a built-in alt-text input, but it is optional, and editors routinely leave it blank when adding images to a Slice. Many front-ends then either omit the alt attribute or fall back to the image filename. Because the same image Slice repeats across pages, a single habit of skipping alt text produces site-wide failures for screen reader users.

How to fix:

Read alt text from the Image field's `alt` property in your front-end and render it explicitly, never falling back to the filename. Make alt text part of the editorial process: document that every informative image needs it, and use Slice Simulator review or a CI accessibility check to catch blanks before publish. For decorative images, set the field's alt to an empty string deliberately and render alt="" so the image is skipped, rather than leaving the attribute missing.

Before
// Next.js Slice component
<img src={slice.primary.image.url} />
After
// Use the field's alt; never fall back to a filename
const { url, alt, dimensions } = slice.primary.image;
if (alt === null) console.warn(`Missing alt text in ${slice.slice_type}`);
<img src={url} alt={alt ?? ''} width={dimensions?.width} height={dimensions?.height} />
critical

Customized Rich Text Serializer Outputs Divs Instead of Semantic Elements

WCAG 1.3.1

Prismic Rich Text is a structured array of nodes (heading1-heading6, paragraph, list-item, o-list-item, hyperlink) that your serializer maps to HTML. The default @prismicio/client serializer produces semantic HTML, but teams frequently override it to add classes and accidentally map paragraphs or headings to divs, or collapse the heading levels. One wrong line in this single function strips structure from every Rich Text field on the site, so screen reader users lose heading, list, and landmark navigation everywhere.

How to fix:

Keep the serializer's element mapping semantic: heading1 to h1, heading2 to h2 (and so on), paragraph to p, list-item to li inside ul, o-list-item to li inside ol, hyperlink to a. Add only className, never change the tag. Restrict which heading levels editors can insert in each Rich Text field (via the field configuration) so a Slice that renders inside a section cannot emit an h1.

Before
// Custom serializer collapses headings to styled divs
const serializer = {
  heading2: ({ children }) => <div className="h2">{children}</div>,
  paragraph: ({ children }) => <div className="copy">{children}</div>,
};
After
const serializer = {
  heading2: ({ children }) => <h2 className="h2">{children}</h2>,
  heading3: ({ children }) => <h3 className="h3">{children}</h3>,
  paragraph: ({ children }) => <p className="copy">{children}</p>,
  hyperlink: ({ node, children }) => <a href={node.data.url}>{children}</a>,
};
serious

Link Fields and CTA Slices With Generic or Empty Link Text

WCAG 2.4.4

CTA and button Slices typically pair a Link field with a separate label field. With no validation, editors enter 'Click here', 'Read more', or 'Learn more', or leave the label blank, producing links that fail WCAG 2.4.4 Link Purpose when a screen reader user lists links out of context - or empty, focusable links with no accessible name at all.

How to fix:

Require the label field on every CTA/button Slice and render the link text from it. Review pages for repeated generic phrases and rewrite them to describe the destination ('Read the 2026 accessibility report'). If the visible label must stay short for design, add a descriptive accessible name with aria-label that names the destination. Never render a link whose only content is an icon without an accessible name.

Before
<a href={slice.primary.link.url}>{slice.primary.label || 'Read more'}</a>
After
// Require label; give icon-only links an accessible name
<a href={slice.primary.link.url} aria-label={slice.primary.aria_label || undefined}>
  {slice.primary.label}
</a>
serious

Video and Embed Slices Without Captions or Transcripts

WCAG 1.2.2

Prismic stores video as Embed fields (oEmbed from YouTube, Vimeo, Wistia) or Link-to-media fields. The default models have no field for captions, transcripts, or audio description, so video Slices commonly ship with autoplaying or uncaptioned media, failing WCAG 1.2.2 Captions (Prerecorded) and locking out Deaf and hard-of-hearing visitors.

How to fix:

Add a 'transcript' Rich Text field and, for self-hosted media, a 'captionsFile' Link-to-media field (VTT/SRT) to any Slice that contains video, and render the captions via a track element and the transcript below the player. For embedded platforms, require editors to enable captions on the source and confirm them before the embed is approved. Never autoplay video with sound.

Before
<div dangerouslySetInnerHTML={{ __html: slice.primary.video.html }} />
After
<figure>
  <div dangerouslySetInnerHTML={{ __html: slice.primary.video.html }} />
  {slice.primary.transcript && (
    <details><summary>Transcript</summary><PrismicRichText field={slice.primary.transcript} /></details>
  )}
</figure>
serious

Locale Content Rendered Without a Matching Lang Attribute

WCAG 3.1.1

Prismic supports multiple locales, and you request a specific locale from the API per route. Front-ends frequently forget to set the html element's lang attribute to match, so a screen reader reads French or German content with an English speech engine, producing unintelligible audio that fails WCAG 3.1.1 Language of Page.

How to fix:

Map the Prismic locale you fetched to the document's lang attribute on the html element (for example lang="fr-fr" or the language subtag lang="fr"). For a passage in a different language inside an otherwise single-language page, wrap it in an element with its own lang attribute. Drive this from the API locale parameter, not client-side guessing.

Before
// Next.js: html lang hard-coded
export default function Document() {
  return <Html lang="en">...</Html>;
}
After
// Set lang from the Prismic locale you requested
<Html lang={prismicLocaleToLang(locale) /* e.g. 'fr-fr' -> 'fr' */}>...</Html>
moderate

Slice Simulator Reviewed Visually but Never for Accessibility

WCAG 1.3.1

Slice Machine's Slice Simulator lets developers and editors preview a Slice in isolation with mock content. Teams use it to check layout but rarely run accessibility tooling against it, so heading-hierarchy problems, missing alt text, low contrast, and unlabelled controls baked into a Slice are only discovered after the Slice has been reused across many published pages.

How to fix:

Run axe-core against the Slice Simulator (and against full assembled pages, since a Slice in isolation can hide heading-hierarchy and landmark issues). Add an accessibility check to CI that scans rendered Slices, and make 'passes axe with the mock content' part of a Slice's definition of done before it enters the shared library.

Before
// CI only type-checks and builds Slices
npm run build
After
// Add an automated a11y gate for Slices and pages
npx @axe-core/cli http://localhost:9999/  # Slice Simulator
npx @axe-core/cli http://localhost:3000/  # full rendered page
moderate

Repeated Slices Create Duplicate IDs and Broken ARIA References

WCAG 4.1.2

Because a single Slice can appear multiple times on one page, any hard-coded id used for label/control association (aria-controls, aria-labelledby, for/id on form fields, accordion panel ids) is duplicated when the Slice repeats. Duplicate ids break the programmatic relationships assistive technology depends on, so labels, error messages, and expand/collapse state stop being announced correctly.

How to fix:

Generate unique ids per Slice instance using the Slice's id from the API or a React useId hook, and build all aria-controls/aria-labelledby/for references from that unique base. Never hard-code an id inside a Slice template. Test a page that uses the same Slice two or more times with a screen reader to confirm labels and state are still announced.

Before
// Hard-coded id duplicated when the Slice repeats
<button aria-controls="panel" aria-expanded="false">FAQ</button>
<div id="panel">...</div>
After
const uid = useId();
<button aria-controls={`panel-${uid}`} aria-expanded="false">FAQ</button>
<div id={`panel-${uid}`} role="region">...</div>

Prismic-Specific Tips

  • Treat the Rich Text serializer as a site-wide accessibility control. It is one function that decides heading, list, and link semantics for every Rich Text field - keep its element mapping intact and only add classes.
  • Make alt text non-optional in practice: read the Image field's alt in the front-end, never fall back to filenames, and gate publishing on a check that flags blank alt on informative images.
  • Build accessibility into the Slice library, not individual pages. A Slice that passes axe before it enters the shared library protects every page that reuses it; a broken one fails them all.
  • Generate unique ids per Slice instance (Slice id or useId) for every aria-controls, aria-labelledby, and for/id relationship, because a Slice can appear several times on one page.
  • Restrict heading levels per Rich Text field in the field configuration so a Slice rendered inside a page section cannot emit an h1 and break the outline.
  • Run axe-core against both the Slice Simulator and full assembled pages, and test multi-locale routes with the html lang attribute set to the locale you fetched.

axe DevTools

Browser extension and CI tool for automated WCAG testing. Essential for catching Rich Text serializer bugs, missing alt-text propagation, duplicate-id issues from repeated Slices, and locale mismatches on Prismic-driven front-ends.

@prismicio/react PrismicRichText

The official React component for rendering Prismic Rich Text. Produces semantic HTML by default; customize its components map carefully so you never replace headings, paragraphs, or lists with non-semantic elements.

WebAIM Contrast Checker

Check text, link, and button colors defined in your front-end theme and any color fields exposed in Slices against the 4.5:1 and 3:1 minimums, including text placed over background images in hero Slices.

NVDA + Firefox / VoiceOver + Safari

Manual screen-reader testing verifies heading announcements, link purpose, form labelling, and that repeated Slices still expose correct labels and state - things automated scanners cannot fully judge.

Lighthouse (Chrome DevTools)

Built-in Chrome audit that catches common Prismic front-end issues like low contrast and missing accessible names. Treat a high score as necessary but not sufficient and pair it with manual keyboard and screen-reader testing.

Prismic Accessibility At a Glance

Plugin / Tool AreaCommon FailureWCAGBest Fix
Image Fields across Slices Optional alt left blank, filename fallback1.1.1Render field alt; gate publish on blank-alt check
Rich Text Serializer site-wide text Custom serializer outputs divs1.3.1Keep semantic mapping; add classes only
CTA / Link Slices buttons & links Generic or empty link text2.4.4Require descriptive labels; name icon links
Locales multi-language lang attribute not set to locale3.1.1Map fetched locale to html lang
Repeated Slices same Slice reused Duplicate hard-coded ids break ARIA4.1.2Generate unique ids per instance (useId)

Frequently Asked Questions

Where do accessibility problems actually come from on a Prismic site?

Two places, and they fail independently. The first is the content model and Slice library: image fields with optional alt text, CTA Slices that allow generic 'Read more' labels, video Slices with no field for captions or transcripts, and heading levels that editors can misuse. The second is the front-end that consumes the Prismic API - most importantly the Rich Text serializer, a single function that maps Prismic's structured text nodes to HTML and silently controls heading, list, and link semantics for the entire site. A perfect content model can be undone by a serializer that outputs divs, and perfect front-end code can be undone by a model that never collects alt text. Fix both: keep the serializer semantic, make alt text effectively required, and gate publishing on an automated accessibility check.

Why does the same Prismic Slice break accessibility on multiple pages at once?

Because Slices are reusable components: one Slice definition renders across dozens or hundreds of pages, so any accessibility defect in it - a missing alt fallback, a wrong heading tag, a hard-coded id, an unlabelled control - repeats everywhere the Slice is used. The flip side is leverage: fix the Slice once and every page that uses it improves. That is why the practical advice for Prismic is to build accessibility into the shared Slice library and make 'passes axe in the Slice Simulator with mock content' part of a Slice's definition of done, rather than auditing finished pages one by one. Also watch for hard-coded ids inside a Slice, since a Slice can appear more than once on a single page and duplicate ids break ARIA relationships.

Does Prismic help with European Accessibility Act compliance?

Prismic is neutral - it gives you the fields and rendering control to build a conformant site but does not guarantee one. The European Accessibility Act applies to businesses providing covered products and services to consumers in the EU and points to WCAG (via EN 301 549) as the technical benchmark, generally WCAG 2.1 Level AA. On Prismic that means making alt text effectively required, keeping the Rich Text serializer semantic, supplying captions/transcripts for video Slices, setting the html lang attribute to match the locale you fetch, and ensuring repeated Slices keep unique ids and valid ARIA. Treat these as build requirements verified before launch and keep evidence of your testing. This is general information, not legal advice - consult a qualified attorney for your situation.

Further Reading

Other CMS Checklists