feat(a11y): WCAG 2.2 accessibility fixes across four areas
- modal/_validateField: set aria-invalid on invalid inputs so screen readers announce field errors; login.js mirrors this for username/password fields - color pickers (notes, calendar): wrap swatches in role="radiogroup" with aria-labelledby, add aria-checked per swatch, localized aria-labels instead of hex values, roving tabindex with Arrow/Enter/Space keyboard navigation - nav badges: badge spans get aria-hidden="true"; nav link aria-label updated to include overdue count (tasks) or pending reminder count (reminders) - router: remove aria-live from <main> (caused full page re-reads on nav); add dedicated #route-announcer sr-only region with aria-live=polite + aria-atomic, announces page label 50ms after render completes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-2
@@ -267,6 +267,14 @@ async function renderPage(route, previousPath = null) {
|
||||
|
||||
await module.render(pageWrapper, { user: currentUser });
|
||||
|
||||
// Route-Announcer: Screenreader über Seitenwechsel informieren (gezielt, nicht gesamter Inhalt)
|
||||
const announcer = document.getElementById('route-announcer');
|
||||
if (announcer) {
|
||||
const pageLabel = navItems().find((n) => n.path === path)?.label ?? path;
|
||||
announcer.textContent = '';
|
||||
setTimeout(() => { announcer.textContent = pageLabel; }, 50);
|
||||
}
|
||||
|
||||
// Erst nach render() + CSS sichtbar machen und Animation starten
|
||||
pageWrapper.style.opacity = '';
|
||||
pageWrapper.classList.add(inClass);
|
||||
@@ -356,7 +364,6 @@ function renderAppShell(container) {
|
||||
const main = document.createElement('main');
|
||||
main.className = 'app-content';
|
||||
main.id = 'main-content';
|
||||
main.setAttribute('aria-live', 'polite');
|
||||
|
||||
const bottomNav = document.createElement('nav');
|
||||
bottomNav.className = 'nav-bottom';
|
||||
@@ -441,7 +448,13 @@ function renderAppShell(container) {
|
||||
toastContainer.id = 'toast-container';
|
||||
toastContainer.setAttribute('aria-live', 'assertive');
|
||||
|
||||
container.replaceChildren(skipLink, sidebar, main, bottomNav, backdrop, moreSheet, searchOverlay, toastContainer);
|
||||
const routeAnnouncer = document.createElement('div');
|
||||
routeAnnouncer.id = 'route-announcer';
|
||||
routeAnnouncer.className = 'sr-only';
|
||||
routeAnnouncer.setAttribute('aria-live', 'polite');
|
||||
routeAnnouncer.setAttribute('aria-atomic', 'true');
|
||||
|
||||
container.replaceChildren(skipLink, sidebar, main, bottomNav, backdrop, moreSheet, searchOverlay, toastContainer, routeAnnouncer);
|
||||
|
||||
// Klick-Handler für alle Nav-Links
|
||||
container.querySelectorAll('[data-route]').forEach((el) => {
|
||||
|
||||
Reference in New Issue
Block a user