# Dostępność — widget a11y (WCAG) Widget dostępności: pływający przycisk otwierający panel z opcjami dla osób z niepełnosprawnościami (rozmiar tekstu, kontrast, skala szarości, podkreślone linki, czytelna czcionka, wyłączenie animacji, duży kursor). > **OPCJONALNY — nie dodawaj domyślnie.** Widget a11y jest wymagany prawnie > TYLKO dla niektórych stron (podmioty publiczne, część e-commerce/usług objętych > European Accessibility Act). Dla większości stron komercyjnych to OPCJA, nie > obowiązek. Dodawaj GDY klient/projekt tego wymaga — nie na każdej stronie z > automatu. W razie wątpliwości: zapytaj, czy strona podlega wymogom dostępności. Wzorzec jak CookieBanner: provider + widget, wpinasz raz. Preferencje w cookie (bez flash), stosowane jako atrybuty `data-a11y-*` na ``. > **Granica plugin/projekt:** plugin dostarcza MECHANIZM (widget, stan, cookie, > atrybuty na html). Projekt dostarcza CSS reagujący na atrybuty — bo style > zależą od designu projektu (kolory, czcionki, Tailwind). Plugin NIE narzuca > stylów, żeby nie kolidować. Gotowy CSS do skopiowania niżej. --- ## 1. Wpięcie — Provider + Widget ```tsx // app/(frontend)/[locale]/layout.tsx import { AccessibilityProvider, AccessibilityWidget } from '@intecion/ipal-kit/client' {children} ``` Widget jest bez stylów (jak CookieBanner) — classNames dopasowujesz do designu. --- ## 2. CSS reagujący na atrybuty (OBOWIĄZKOWE — projekt) Widget ustawia atrybuty na ``. Bez tego CSS nic się nie dzieje. Skopiuj do globalnego CSS projektu (dostosuj do designu): ```css /* Rozmiar tekstu */ html[data-a11y-text="1"] { font-size: 112.5%; } html[data-a11y-text="2"] { font-size: 125%; } html[data-a11y-text="3"] { font-size: 150%; } /* Odstęp między liniami */ html[data-a11y-line="1"] * { line-height: 1.8 !important; } html[data-a11y-line="2"] * { line-height: 2.2 !important; } /* Kontrast wysoki */ html[data-a11y-contrast="high"] { filter: contrast(1.4); } /* Kontrast odwrócony */ html[data-a11y-contrast="inverted"] { filter: invert(1) hue-rotate(180deg); } html[data-a11y-contrast="inverted"] img, html[data-a11y-contrast="inverted"] video { filter: invert(1) hue-rotate(180deg); /* przywróć media */ } /* Skala szarości */ html[data-a11y-grayscale="on"] { filter: grayscale(1); } /* Uwaga: filter na html nie kumuluje się — jeśli łączysz kontrast+szarość, zastosuj na body albo połącz w jednej regule. */ /* Podkreślone linki */ html[data-a11y-underline="on"] a { text-decoration: underline !important; } /* Czytelna czcionka (podmień na swoją dyslexia-friendly / prostą) */ html[data-a11y-font="readable"] * { font-family: Verdana, Tahoma, sans-serif !important; letter-spacing: 0.02em; } /* Wyłączenie animacji */ html[data-a11y-motion="reduce"] *, html[data-a11y-motion="reduce"] *::before, html[data-a11y-motion="reduce"] *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } /* Duży kursor */ html[data-a11y-cursor="big"] * { cursor: url('/cursors/big.svg') 4 4, auto !important; } ``` Dostosuj wartości do projektu (kolory kontrastu, czcionka, kursor). To Twój CSS — plugin tylko ustawia atrybuty. > **Kontrast + filter:** wiele opcji używa `filter` na ``. CSS `filter` na > jednym elemencie NIE kumuluje wielu wartości z różnych reguł — ostatnia wygrywa. > Jeśli chcesz łączyć (np. szarość + kontrast), zastosuj filtry na `body` z > pełną wartością, albo zbuduj reguły kombinowane. Dla pojedynczych opcji działa > bez problemu. --- ## 3. Bez flash (SSR) — opcjonalne Domyślnie widget stosuje atrybuty po hydratacji (krótki flash przy ładowaniu, jeśli użytkownik miał ustawienia). Żeby tego uniknąć, odczytaj cookie server-side i ustaw atrybuty na `` w SSR: ```tsx // layout.tsx (server) — odczytaj cookie i ustaw atrybuty od razu import { cookies } from 'next/headers' import { A11Y_COOKIE, parseA11y, a11yAttributes } from '@intecion/ipal-kit' const raw = (await cookies()).get(A11Y_COOKIE)?.value const attrs = a11yAttributes(parseA11y(raw)) const htmlAttrs = Object.fromEntries( Object.entries(attrs).filter(([, v]) => v !== null), ) return ... ``` Provider i tak re-aplikuje na kliencie i synchronizuje. To tylko eliminuje flash. --- ## 4. Osobny przycisk otwierający (opcjonalnie) Widget ma wbudowany pływający przycisk. Jeśli chcesz otwierać panel z innego miejsca (np. stopka „Dostępność"), użyj hooka: ```tsx 'use client' import { useAccessibility } from '@intecion/ipal-kit/client' // stan otwarcia trzymaj sam, albo rozbuduj widget — hook daje state/set/reset ``` --- ## 5. Compliance — kiedy dostępność jest wymagana Dostępność (WCAG) jest wymagana prawnie TYLKO dla części stron: - **Podmioty publiczne** (urzędy, szkoły, instytucje) — ustawa o dostępności cyfrowej - **Duże e-commerce / usługi** objęte European Accessibility Act (2019/882, od 2025) - Strony, gdzie klient sam tego wymaga (polityka firmy, przetarg) Dla **większości stron komercyjnych** (wizytówka, mała firma, katalog) widget a11y to **opcja, nie obowiązek** — dodawaj gdy klient wymaga, nie z automatu. Gdy dodajesz: widget sam w sobie NIE czyni strony w pełni dostępną — to pomoc dla użytkownika. Pełna dostępność to też semantyczny HTML, alt teksty, nawigacja klawiaturą, kontrast bazowy. Widget uzupełnia, nie zastępuje. Nie sprzedawaj klientowi „mamy widget = jesteśmy zgodni z WCAG". Patrz wymagania-prawne.md.