From 2b59a10c147c5545362afb420f434b49faaf9196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 21 Apr 2026 21:12:16 +0400 Subject: [PATCH] Add ARIA attributes and focus management to cookie banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve accessibility across the cookie banner components: add aria-modal/aria-labelledby/aria-describedby on dialog cards, aria-label on icon-only buttons and category toggles, aria-expanded on cookie detail disclosure, aria-hidden on decorative SVGs, and auto-focus into dialogs when they become visible. Signed-off-by: Émile Ré --- .../cookie-banner/src/components/banner.ts | 3 +++ packages/cookie-banner/src/components/base.ts | 9 +++++++++ .../src/components/category-toggle.ts | 1 + .../src/components/preference-panel.ts | 1 + .../src/components/settings-button.ts | 4 ++-- .../src/themed-banner/themed-banner.ts | 20 ++++++++++--------- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/cookie-banner/src/components/banner.ts b/packages/cookie-banner/src/components/banner.ts index 17a6fc057..4f33dc76f 100644 --- a/packages/cookie-banner/src/components/banner.ts +++ b/packages/cookie-banner/src/components/banner.ts @@ -27,6 +27,9 @@ export class ProboBanner extends ProboElement { private onStateChange = (e: Event): void => { const { state } = (e as CustomEvent).detail; this.hidden = state !== "banner"; + if (state === "banner") { + this.focusFirst(); + } }; connectedCallback(): void { diff --git a/packages/cookie-banner/src/components/base.ts b/packages/cookie-banner/src/components/base.ts index ba9e3dd1c..b5210b2d0 100644 --- a/packages/cookie-banner/src/components/base.ts +++ b/packages/cookie-banner/src/components/base.ts @@ -20,7 +20,16 @@ export interface ConsentDraft { [category: string]: boolean; } +const FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'; + export class ProboElement extends HTMLElement { + protected focusFirst(): void { + requestAnimationFrame(() => { + const el = this.querySelector(FOCUSABLE); + el?.focus(); + }); + } + protected findAncestor(tagName: string): T | null { let el: HTMLElement | null = this.parentElement; while (el) { diff --git a/packages/cookie-banner/src/components/category-toggle.ts b/packages/cookie-banner/src/components/category-toggle.ts index d5b15f8a6..267952689 100644 --- a/packages/cookie-banner/src/components/category-toggle.ts +++ b/packages/cookie-banner/src/components/category-toggle.ts @@ -49,6 +49,7 @@ export class ProboCategoryToggle extends ProboElement { if (!this.category || !this.root) return; const name = this.category.categoryName; + this.checkbox.setAttribute("aria-label", name); const isRequired = this.category.kind === "NECESSARY"; if (isRequired) { diff --git a/packages/cookie-banner/src/components/preference-panel.ts b/packages/cookie-banner/src/components/preference-panel.ts index 552947acb..989f8d013 100644 --- a/packages/cookie-banner/src/components/preference-panel.ts +++ b/packages/cookie-banner/src/components/preference-panel.ts @@ -28,6 +28,7 @@ export class ProboPreferencePanel extends ProboElement { this.hidden = state !== "panel"; if (state === "panel") { (this.root as ProboCookieBannerRoot).resetDraft(); + this.focusFirst(); } }; diff --git a/packages/cookie-banner/src/components/settings-button.ts b/packages/cookie-banner/src/components/settings-button.ts index 7b06c6fd5..ab0f19f47 100644 --- a/packages/cookie-banner/src/components/settings-button.ts +++ b/packages/cookie-banner/src/components/settings-button.ts @@ -67,8 +67,8 @@ export class ProboSettingsButton extends HTMLElement { .icon { display: flex; flex-shrink: 0; } ::slotted(*) { display: contents; } - `; diff --git a/packages/cookie-banner/src/themed-banner/themed-banner.ts b/packages/cookie-banner/src/themed-banner/themed-banner.ts index 0e9d675ff..7a812dcbe 100644 --- a/packages/cookie-banner/src/themed-banner/themed-banner.ts +++ b/packages/cookie-banner/src/themed-banner/themed-banner.ts @@ -17,8 +17,8 @@ import type { ProboCookieBannerRoot } from "../components/cookie-banner-root"; import type { BannerConfig } from "../client"; import { THEMED_STYLES } from "./styles"; -const CLOSE_ICON = ``; -const CHEVRON_DOWN = ``; +const CLOSE_ICON = ``; +const CHEVRON_DOWN = ``; export class ProboThemedBanner extends HTMLElement { private shadow: ShadowRoot; @@ -52,9 +52,9 @@ export class ProboThemedBanner extends HTMLElement {
-