171 lines
6.0 KiB
Markdown
171 lines
6.0 KiB
Markdown
# 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
|
|
|
|
```tsx
|
|
// 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):
|
|
|
|
```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 `<html>`. 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 `<html>` 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 <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:
|
|
|
|
```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. |