6.0 KiB
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 <html>.
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
// app/(frontend)/[locale]/layout.tsx
import { AccessibilityProvider, AccessibilityWidget } from '@intecion/ipal-kit/client'
<AccessibilityProvider>
<body>
{children}
<AccessibilityWidget
classNames={{
button: 'a11y-button',
panel: 'a11y-panel',
row: 'a11y-row',
label: 'a11y-label',
control: 'a11y-control',
active: 'a11y-active',
resetButton: 'a11y-reset',
closeButton: 'a11y-close',
}}
texts={{ title: 'Dostępność', reset: 'Resetuj' }} // opcjonalne, PL domyślnie
/>
</body>
</AccessibilityProvider>
Widget jest bez stylów (jak CookieBanner) — classNames dopasowujesz do designu.
2. CSS reagujący na atrybuty (OBOWIĄZKOWE — projekt)
Widget ustawia atrybuty na <html>. Bez tego CSS nic się nie dzieje. Skopiuj do
globalnego CSS projektu (dostosuj do designu):
/* 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
filterna<html>. CSSfilterna jednym elemencie NIE kumuluje wielu wartości z różnych reguł — ostatnia wygrywa. Jeśli chcesz łączyć (np. szarość + kontrast), zastosuj filtry nabodyz 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 <html> w SSR:
// 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 <html lang={locale} {...htmlAttrs}>...</html>
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:
'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.