diff --git a/.changeset/orange-trainers-learn.md b/.changeset/orange-trainers-learn.md new file mode 100644 index 0000000000..8b6e059bd1 --- /dev/null +++ b/.changeset/orange-trainers-learn.md @@ -0,0 +1,5 @@ +--- +"astro": minor +--- + +Improves the a11y-missing-content rule and error message for audit feature of dev-overlay. This also fixes an error where this check was falsely reporting accessibility errors. diff --git a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts index 5a1fd46938..537b03e00d 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts @@ -358,13 +358,72 @@ export const a11y: AuditRuleWithSelector[] = [ }, { code: 'a11y-missing-content', - title: 'Missing content on element important for accessibility', - message: 'Headings and anchors must have content to be accessible.', + title: 'Missing content', + message: + 'Headings and anchors must have an accessible name, which can come from: inner text, aria-label, aria-labelledby, an img with alt property, or an svg with a tag .', selector: a11y_required_content.join(','), match(element: HTMLElement) { // innerText is used to ignore hidden text const innerText = element.innerText.trim(); - if (innerText === '') return true; + if (innerText !== '') return false; + + // Check for aria-label + const ariaLabel = element.getAttribute('aria-label')?.trim(); + if (ariaLabel && ariaLabel !== '') return false; + + // Check for valid aria-labelledby + const ariaLabelledby = element.getAttribute('aria-labelledby')?.trim(); + if (ariaLabelledby) { + const ids = ariaLabelledby.split(' '); + for (const id of ids) { + const referencedElement = document.getElementById(id); + if (referencedElement && referencedElement.innerText.trim() !== '') return false; + } + } + + // Check for with valid alt attribute + const imgElements = element.querySelectorAll('img'); + for (const img of imgElements) { + const altAttribute = img.getAttribute('alt'); + if (altAttribute && altAttribute.trim() !== '') return false; + } + + // Check for with valid title + const svgElements = element.querySelectorAll('svg'); + for (const svg of svgElements) { + const titleText = svg.querySelector('title'); + if (titleText && titleText.textContent && titleText.textContent.trim() !== '') return false; + } + + const inputElements = element.querySelectorAll('input'); + for (const input of inputElements) { + // Check for alt attribute if input type is image + if (input.type === 'image') { + const altAttribute = input.getAttribute('alt'); + if (altAttribute && altAttribute.trim() !== '') return false; + } + + // Check for aria-label + const inputAriaLabel = input.getAttribute('aria-label')?.trim(); + if (inputAriaLabel && inputAriaLabel !== '') return false; + + // Check for aria-labelledby + const inputAriaLabelledby = input.getAttribute('aria-labelledby')?.trim(); + if (inputAriaLabelledby) { + const ids = inputAriaLabelledby.split(' '); + for (const id of ids) { + const referencedElement = document.getElementById(id); + if (referencedElement && referencedElement.innerText.trim() !== '') return false; + } + } + + // Check for title + const title = input.getAttribute('title')?.trim(); + if (title && title !== '') return false; + } + + // If all checks fail, return true indicating missing content + return true; }, }, {