From 3a4d5ec8001ebf95c917fdc0d186d29650533d93 Mon Sep 17 00:00:00 2001 From: Oliver Speir <115520730+OliverSpeir@users.noreply.github.com> Date: Wed, 17 Jan 2024 06:10:08 -0700 Subject: [PATCH] Update a11y.ts (#9567) * improve error message and allow for aria-label, aria-labelledby, img alt and svg title * add checks for input * Update packages/astro/src/runtime/client/dev-overlay/plugins/audit/a11y.ts Co-authored-by: Emanuele Stoppa * Update .changeset/orange-trainers-learn.md Co-authored-by: Sarah Rainsberger --------- Co-authored-by: Emanuele Stoppa Co-authored-by: Sarah Rainsberger --- .changeset/orange-trainers-learn.md | 5 ++ .../client/dev-toolbar/apps/audit/a11y.ts | 65 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 .changeset/orange-trainers-learn.md diff --git a/.changeset/orange-trainers-learn.md b/.changeset/orange-trainers-learn.md new file mode 100644 index 000000000000..8b6e059bd1b7 --- /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 5a1fd4693893..537b03e00d39 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; }, }, {