Accessibility

Markdown
Loading…

What the components guarantee for keyboard, focus and screen-reader users, and what is still yours to check.

This page covers keyboard use, focus, screen-reader labels and reduced motion for the ready-made components. It describes specific behaviours, not a blanket "WCAG compliant" claim. Everything here is written so you can hand it to an accessibility reviewer.

What the components guarantee

ComponentRole and nameKeyboardAnnounced
Bannerrole="dialog", aria-modal="true", named by its titleDoes not take focus when it appears; the first Tab from the page moves into it, and Tab then stays inside until the visitor answers or closes it. Order: Close, Customise, Reject All, Accept All (CCPA: Close, Do Not Sell)Its title, 700 ms after it appears, so it is not lost behind the page-load announcement
Preferences dialogrole="dialog", aria-modal="true"Focus moves onto the dialog when it opens. Tab wraps inside. Escape closesIts name on open
Opt-out dialogrole="dialog", aria-modal="true"Same as the preferences dialogIts name on open. After an opt-out, focus moves to the confirmation, announced as role="status" together with the note that the dialog closes in ten seconds
Category switchesrole="switch" with aria-checked, named by the categoryIn your configured order. A required category is plain text, not a disabled control
Recall buttonButton, named recallButtonLabel, marked as opening a dialogA single stop
Reload noticerole="alert"Two buttonsImmediately, when it appears

Two behaviours apply to all of it:

  • Focus returns to the button that opened a dialog, whether it closed by Save, Cancel or Escape, and even when that button was replaced in the meantime. This works for your own triggers too, as long as you open the dialog with useConsentActions().
  • After the banner is answered or closed, focus moves to the recall button, so the visitor is never left nowhere. The banner's close button hides it for this page view without saving anything; it comes back on the next page load. Customise, Do Not Sell and the recall button are marked as opening a dialog (aria-haspopup="dialog").
  • Every label comes from the translation map, so a German site announces its dialogs in German. See Translations.

Why the banner is first in Tab order

The banner and the recall button render into a container placed as the first child of <body>, wherever you put <CookieYesRoot /> in your layout. A keyboard user reaches the banner on the first Tab instead of after your whole navigation. This is also why the banner is not where you put it in the DOM.

Reduced motion

When the visitor's device asks for reduced motion, every entrance and exit animation is removed: banner, dialogs, recall button, reload notice. Everything still appears and works the same. If you replace the SDK's CSS, keep that rule:

app/consent.css
@media (prefers-reduced-motion: reduce) {
  [data-cy-part] {
    animation: none;
    transition: none;
  }
}

Not covered

Three things stay with you:

  • Colour contrast once you set your own colours. theme.primaryColor is the same in light and dark mode, so a colour that passes on white can fail on the dark background. See Colour scheme.
  • Text size and zoom, which depend on your page.
  • Whether rejecting is as easy as accepting. Regulators check this; it is a design decision in your hands.

Check it in a minute

Clear the consent cookie so the banner shows, click the address bar so focus starts outside the page, put the mouse aside.

#Do thisYou should see
1Press Tab onceFocus on the banner's Close button, not on a nav link
2Keep pressing TabCustomise → Reject All → Accept All → "Powered by" link, then back to Close. Focus never leaves the banner
3Go back to Customise, press EnterThe dialog opens with focus on the dialog itself
4Tab past the last controlFocus wraps to the first control; it never reaches the page behind
5Press EscapeThe dialog closes and focus is back on Customise

Repeat step 5 with Save and with Cancel: all three must return focus to Customise. At every step the focused control must show a visible outline; a CSS reset that removes :focus-visible breaks the whole flow without any test noticing.

If you build your own UI

The behaviour above lives in Banner.Root, Preferences.Root, OptOut.Root and useConsentActions(), so building on the primitives keeps it. Building on hooks alone means you provide it yourself:

  • The banner and the dialogs hold focus: Tab and Shift+Tab wrap inside.
  • Escape closes a dialog.
  • Focus returns to the opener on close.
  • The dialog container has tabIndex={-1} so it can receive focus without being a tab stop.
  • Every control shows a visible focus outline.
  • Reduced motion is honoured.

Common mistakes

Focus jumps to the top of the page when a dialog closes. The dialog was opened through the runtime directly. Open it with useConsentActions().showPreferences().

Tab from the address bar reaches every nav link before the banner. A custom banner without Banner.Root, which is what places it first in <body>.

Contrast fails in dark mode only. theme.primaryColor does not change between schemes. Set it per scheme in CSS: see Colour scheme.

Next steps

On this page