Back to Blog
Your Cookie Banner Is the First Accessibility Failure Every Visitor Hits
GDPR forced cookie banners onto every European site. Most of them are accessibility disasters that block screen-reader users from your content entirely. Here is what breaks, what to fix, and how the major CMP vendors compare.
11 min read
Why cookie banners fail more than other UI
A normal modal on your site appears when the user does something — clicks a button, opens a menu. The user has context for it, they expect it, and they are usually focused on a related action.
A cookie banner appears the instant the page loads, while the user is mid-scroll-or-tab in pursuit of something completely different. The banner is structurally an interruption. To be accessible, it has to do four things at once that most UI components rarely have to do simultaneously:
- Steal focus in a way that does not feel like an attack
- Announce itself to assistive technology as a dialog requiring action
- Trap focus while open so the user cannot tab past it into hidden content
- Release focus correctly to the original content position when dismissed
Every one of these is a place where banner implementations break, and the failure modes compound.
The five most common failure modes
1. The banner is a <div>, not a <dialog>
WCAG 4.1.2, 1.3.1 · partial automation
The single most common pattern. The banner sits in the DOM as a styled <div> with position: fixed. Screen readers treat it as a content block, not a dialog. There is no announcement, no role, no understanding that the user must respond.
<!-- ❌ Bad: just a div, screen readers may not even announce its appearance -->
<div class="cookie-banner">
<p>We use cookies. Do you accept?</p>
<button>Accept</button>
<button>Reject</button>
</div>
<!-- ✅ Good: dialog with role, modal, and labelled -->
<div role="dialog" aria-modal="true" aria-labelledby="cookie-title" aria-describedby="cookie-desc">
<h2 id="cookie-title">Cookie consent</h2>
<p id="cookie-desc">We use cookies for analytics and personalisation. You can accept all, reject non-essential cookies, or customise your choices.</p>
<button>Accept all</button>
<button>Reject non-essential</button>
<button>Customise</button>
</div>
Native <dialog> element is also valid (and arguably better since it handles focus trap and ESC for free) — see the top 10 violations post for the native dialog pattern.
2. Focus is not trapped inside the banner
WCAG 2.4.3, 2.1.2 · manual testing required
The user tabs forward, expecting to navigate the banner. After three or four Tabs, focus escapes the banner into the hidden page behind it — sometimes visibly, sometimes not. The user is now interacting with content they cannot see, content that they have not given cookie consent for, and content that may not even be rendered if there is loading state.
The fix in vanilla JavaScript:
function trapFocus(dialog) {
const focusable = dialog.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
dialog.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
}
Native <dialog> with showModal() does this for free. If you use a framework, libraries like Radix UI, Headless UI, and the @react-aria/dialog hook all handle focus trapping.
3. Buttons have no accessible names
WCAG 4.1.2 · axe rule: button-name
Either the buttons are icon-only (a checkmark and an X), or they have generic text ("Accept" / "Reject") that doesn't say what is being accepted or rejected. Both fail.
<!-- ❌ Bad: icon-only, no name -->
<button class="accept-btn"><svg>...</svg></button>
<!-- ❌ Bad: ambiguous text -->
<button>OK</button>
<!-- ✅ Good: explicit, contextual -->
<button>Accept all cookies</button>
<button>Reject non-essential cookies</button>
<button>Customise cookie preferences</button>
4. The banner cannot be dismissed by keyboard
WCAG 2.1.1 · manual testing required
There is a close-X icon-button at the top right of the banner that only responds to mouse clicks. Keyboard users have no way to close the banner without first answering the consent question — which is fine, except many sites treat closing the banner as implicit acceptance, hidden behind the X.
The fix: Esc dismisses the banner, treated as "reject non-essential" (not as accept). Plus the close button is a real <button> with aria-label and keyboard handling.
<button aria-label="Close cookie banner without accepting">×</button>
dialog.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
rejectNonEssential();
closeBanner();
}
});
5. The banner blocks the rest of the page
WCAG 1.3.1, 4.1.2 · manual + automated
Even when the banner has the right ARIA roles, sometimes the rest of the page is left fully focusable. Tab from the banner and you land on links behind it that the user can read with a screen reader but cannot see (because the banner overlays them). Inversely, sometimes everything else has aria-hidden="true" applied incorrectly, hiding content from screen readers but also hiding it from sighted users in unintended ways.
The cleanest pattern is the native <dialog> element with showModal(), which handles inert state on the rest of the page automatically. Modern browsers (Chrome 105+, Firefox 110+, Safari 16+) all support it.
<dialog id="cookie-dialog" aria-labelledby="cookie-title">
<h2 id="cookie-title">Cookie consent</h2>
<!-- ... -->
</dialog>
<script>
document.getElementById('cookie-dialog').showModal();
</script>
For older browser support or richer customisation, use a tested library — listed below.
How the major CMP vendors compare in 2026
This is a snapshot; vendors update frequently. Verify your current configuration against your live banner. None of these is a guaranteed pass — your custom configuration is the live test.
| CMP | Default banner accessibility | Common gotchas |
|---|---|---|
| Cookiebot (Usercentrics) | Generally good in default templates; explicit accessibility documentation. | Custom CSS that overrides their built-in colours can break contrast; their "minimal" template strips out some labelled elements. |
| OneTrust | Adequate; depends heavily on template version and configuration. | Older templates lack proper role="dialog", multi-language switches not always announced. |
| Iubenda | Acceptable in 2026 default banners; older configurations weaker. | Per-purpose toggles in the "Customise" panel sometimes lack labels; check on second-level settings panel. |
| Cookieyes / Termly | Mixed; recent templates improved. | Default position (bottom strip) often pushed below screen-reader announcement order; verify placement in DOM. |
| Custom-built banners | Whatever you built it to be. | Almost always weaker than tested CMPs unless built by an accessibility-fluent team. |
Practical recommendation: if you can use a major CMP with their accessibility-tested default template, do that and resist customising the styling beyond the minimum. If your design team insists on heavy customisation, budget for an accessibility test of the resulting banner — every theme change is a regression risk.
The minimal accessible banner — copy-paste version
If you want to ship something solid quickly, here is a self-contained accessible cookie banner using the native <dialog> element. It is not a full CMP — you still need to wire up your specific cookie/storage logic — but the UI shell is sound.
<!-- HTML -->
<dialog id="cookie-banner" aria-labelledby="cookie-title" aria-describedby="cookie-desc">
<h2 id="cookie-title">Cookie preferences</h2>
<p id="cookie-desc">
We use essential cookies to make this site work. Optional cookies for
analytics help us understand how the site is used.
</p>
<div class="cookie-buttons">
<button id="accept-all" type="button">Accept all cookies</button>
<button id="reject-optional" type="button">Reject optional cookies</button>
<button id="customise" type="button">Customise</button>
</div>
</dialog>
/* CSS */
#cookie-banner[open] {
position: fixed;
bottom: 1rem;
left: 1rem;
right: 1rem;
max-width: 480px;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 8px;
background: #fff;
box-shadow: 0 4px 24px rgba(0,0,0,0.15);
/* ensure focus rings are visible */
outline: none;
}
#cookie-banner button {
padding: 0.6rem 1rem;
font: inherit;
cursor: pointer;
}
#cookie-banner button:focus-visible {
outline: 2px solid #0066ff;
outline-offset: 2px;
}
.cookie-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 1rem;
}
// JS
const dialog = document.getElementById('cookie-banner');
if (!localStorage.getItem('cookie-consent')) {
dialog.showModal();
}
document.getElementById('accept-all').addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'all');
dialog.close();
// Initialise analytics, etc.
});
document.getElementById('reject-optional').addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'essential-only');
dialog.close();
});
document.getElementById('customise').addEventListener('click', () => {
// Open second-level dialog for granular consent
});
// Esc closes the dialog and counts as "reject optional"
dialog.addEventListener('cancel', (e) => {
e.preventDefault(); // override default close
localStorage.setItem('cookie-consent', 'essential-only');
dialog.close();
});
This is the floor — not the ceiling. A proper CMP additionally handles per-vendor consent records, regional variation (different rules in different EU countries), prior consent storage with audit trail, and so on. But the UI shell above is more accessible than ~70% of the CMPs deployed in production today.
For the broader picture of why this matters, see the top 10 EAA violations, and for the per-country enforcement reality, the EAA fines guide and the France-specific enforcement notes.
How accessible is your cookie banner right now?
Webply's free scan tests your homepage with the cookie banner open, flags every missing label, missing dialog role, and unreachable focus state — and ranks each issue by EAA legal risk per country.
Sources
- European Data Protection Board — Guidelines 05/2020 on consent under GDPR.
- WAI-ARIA Authoring Practices — Modal Dialog pattern.
- MDN —
<dialog>element and HTMLDialogElement interface. - EN 301 549 v3.2.1 — chapter 9 (web content) and chapter 11 (software).
- French Auchan/Carrefour/E.Leclerc/Picard enforcement actions, November 2025.
The CMP comparison reflects market observation in 2026 and is not a vendor endorsement or warranty. Verify your live banner with assistive technology testing — vendor compliance status changes with each release.
Frequently asked questions
Are cookie banners legally required to be accessible?
If your site is in EAA scope (online shops, booking, banking, transport ticketing, paid memberships, etc.), yes — the cookie banner is part of your site and covered by EAA / EN 301 549 / WCAG 2.1 AA exactly like any other content. For sites not in EAA scope, accessibility of the banner is still good practice but not a legal obligation under EU accessibility law. For in-scope sites, cookie banners are also one of the most cited issues in EU enforcement processes because they are the first thing every visitor encounters — if your banner traps focus or lacks accessible labels, a screen-reader user literally cannot reach your content.
My CMP says it's "accessibility-compliant" — am I covered?
Maybe. CMP vendors typically refer to their default UI components meeting WCAG 2.1 AA, not your specific configuration. If you have customised the banner's colours, layout, button text, or position, you may have introduced violations the vendor cannot fix. The only way to know is to test your specific live banner with a screen reader and an automated scanner. We recommend doing this every time you change CMP configuration.
Can I just hide the cookie banner from screen readers?
No, and this is one of the worst patterns I see. Some teams add aria-hidden="true" to the banner so screen readers skip it. This is a violation of GDPR consent rules (you cannot collect consent from users you have hidden the consent UI from) AND of WCAG 1.3.1 (the banner is content, hiding it from assistive tech is discrimination). The banner must be accessible — fix it, do not hide it.
How do I test my cookie banner?
Three quick checks. (1) Tab through your homepage starting from a fresh load — does focus enter the banner first, can you navigate it with Tab, can you escape it with Esc, does focus return sensibly when you accept/reject? (2) Use a screen reader (NVDA on Windows is free) — does it announce the banner as a dialog, are the buttons labelled, can you close it without a mouse? (3) Run an automated scan focused on the homepage with the banner open — most automated tools will flag missing labels and aria-modal issues.
Should I use auto-accept on a delay if the user doesn't respond?
No, never. Auto-accept after a delay (sometimes called "implied consent" or "scroll consent") is illegal under GDPR and ePrivacy — consent must be active, specific, and freely given. It is also irrelevant to accessibility — the inaccessibility of the banner does not justify removing the consent step. Fix the banner so it is accessible, then keep the consent process explicit.
What about cookie walls (block site until consent)?
Cookie walls — refusing access to content unless the user accepts cookies — are problematic on two fronts. The European Data Protection Board has stated they are generally not GDPR-compliant because consent given under that pressure is not freely given. From an accessibility perspective they amplify the problem: a non-functional banner becomes a complete barrier to your entire site. Avoid them.