Recognize aria-hidden="true" as decorative, add class-name id fallback

Elements with aria-hidden="true" are already fully removed from the
accessibility tree, equivalent in intent to alt="" - they were
wrongly flagged as errors instead of the same decorative warning
alt="" gets. On role="img" elements with no wrapping link, aria-label,
or (now) aria-hidden exemption, imageIdentifier() also had no way to
build an exclusion key at all, so the exclude click silently did
nothing. Added a className-based fallback identifier as a last
resort so these elements can still be excluded individually.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Sascha 2026-07-13 12:02:11 +02:00
parent f9cfa1c91e
commit 9b76767838
3 changed files with 17 additions and 4 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -71,12 +71,16 @@
// src ist die stabilste Kennung eines Bildes über mehrere Seitenaufrufe hinweg // src ist die stabilste Kennung eines Bildes über mehrere Seitenaufrufe hinweg
if (img.tagName === 'IMG') return img.currentSrc || img.src || null; if (img.tagName === 'IMG') return img.currentSrc || img.src || null;
// Nicht-<img>-Elemente mit role="img" (z.B. Inline-<svg>) haben kein src. // Nicht-<img>-Elemente mit role="img" (z.B. Inline-<svg> oder <div role="img">)
// Fallback: umgebender Link oder aria-label als Kennung. // haben kein src. Fallback: umgebender Link, aria-label, oder als letzte
// Möglichkeit die class (z.B. bei reinen Icon-Fonts/CSS-Icons ohne jeden
// Text). Der instanceIdentifier()-Zähler unterscheidet danach weiterhin
// gleich aussehende Duplikate voneinander.
const link = img.closest ? img.closest('a[href]') : null; const link = img.closest ? img.closest('a[href]') : null;
if (link && link.href) return `role-img:${link.href}`; if (link && link.href) return `role-img:${link.href}`;
const label = img.getAttribute('aria-label'); const label = img.getAttribute('aria-label');
if (label) return `role-img:${label}`; if (label) return `role-img:${label}`;
if (img.className) return `role-img:class:${img.className}`;
return null; return null;
} }
@ -359,6 +363,7 @@
const labelledBy = resolveIdRefs('aria-labelledby', img); const labelledBy = resolveIdRefs('aria-labelledby', img);
const hasTitle = img.hasAttribute('title'); const hasTitle = img.hasAttribute('title');
const hasLongdesc = img.hasAttribute('longdesc'); const hasLongdesc = img.hasAttribute('longdesc');
const isAriaHidden = img.getAttribute('aria-hidden') === 'true';
const hasAccessibleName = hasAriaLabel || describedBy !== null || labelledBy !== null; const hasAccessibleName = hasAriaLabel || describedBy !== null || labelledBy !== null;
const linkPrefix = isInsideLink(img) ? 'Bild in Link: ' : ''; const linkPrefix = isInsideLink(img) ? 'Bild in Link: ' : '';
@ -373,6 +378,14 @@
const isDecorative = altValue.trim().length === 0; const isDecorative = altValue.trim().length === 0;
segments = [linkPrefix, { code: `alt="${altValue}"` }, ...extras]; segments = [linkPrefix, { code: `alt="${altValue}"` }, ...extras];
kind = isDecorative ? 'warning' : 'success'; kind = isDecorative ? 'warning' : 'success';
} else if (isAriaHidden && !hasAccessibleName) {
// aria-hidden="true" entfernt das Element bereits vollständig aus dem
// Accessibility-Tree für Screenreader ist es so, als gäbe es kein
// Bild. Das ist eine gültige Alternative zu alt="" bei dekorativen
// Elementen (v.a. bei role="img" auf Nicht-<img>-Elementen), daher
// hier keine Fehlermeldung, sondern dieselbe Einstufung wie alt="".
segments = [linkPrefix, { code: 'aria-hidden="true"' }, ...extras];
kind = 'warning';
} else if (hasAccessibleName) { } else if (hasAccessibleName) {
const resolvedName = hasAriaLabel ? img.getAttribute('aria-label') : (labelledBy || describedBy); const resolvedName = hasAriaLabel ? img.getAttribute('aria-label') : (labelledBy || describedBy);
segments = [ segments = [