diff --git a/.circleci/config.yml b/.circleci/config.yml index f2f20d6f9d..7b3aa42218 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ defaults: &defaults unix_box: &unix_box docker: - - image: cimg/node:16.18-browsers + - image: cimg/node:18.18-browsers unix_nightly_box: &unix_nightly_box docker: @@ -13,7 +13,7 @@ unix_nightly_box: &unix_nightly_box orbs: puppeteer: threetreeslight/puppeteer@0.1.2 - browser-tools: circleci/browser-tools@1.4.4 + browser-tools: circleci/browser-tools@1.4.8 set_npm_auth: &set_npm_auth run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH @@ -33,9 +33,7 @@ restore_build: &restore_build commands: browser-tools-job: steps: - - browser-tools/install-browser-tools: - # TODO: remove when chromedriver downloads are fixed - chrome-version: 116.0.5845.96 + - browser-tools/install-browser-tools jobs: # Fetch and cache dependencies. @@ -243,15 +241,15 @@ jobs: - <<: *restore_dependency_cache_unix - run: npm run test:rule-help-version - # Test node API - test_node: + # Test jsdom API + test_jsdom: <<: *defaults <<: *unix_box steps: - checkout - <<: *restore_dependency_cache_unix - <<: *restore_build - - run: npm run test:node + - run: npm run test:jsdom # Release a "next" version next_release: @@ -363,7 +361,7 @@ workflows: - test_rule_help_version: requires: - build_unix - - test_node: + - test_jsdom: requires: - build_unix # Verify the sri history is correct @@ -388,7 +386,7 @@ workflows: - test_virtual_rules - build_api_docs - test_rule_help_version - - test_node + - test_jsdom - verify_sri filters: branches: @@ -406,7 +404,7 @@ workflows: - test_virtual_rules - build_api_docs - test_rule_help_version - - test_node + - test_jsdom filters: branches: only: develop diff --git a/.eslintrc.js b/.eslintrc.js index d5dd272f22..71a39bc1ca 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { root: true, extends: ['prettier'], parserOptions: { - ecmaVersion: 2021 + ecmaVersion: 2023 }, env: { node: true, diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..b7ba1c567c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,44 @@ +version: 2 + +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' + open-pull-requests-limit: 10 + commit-message: + prefix: 'chore' + groups: + # Any updates not caught by the group config will get individual PRs + gha-low-risk: + update-types: + - 'minor' + - 'patch' + + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'monthly' + open-pull-requests-limit: 10 + commit-message: + prefix: 'chore' + ignore: + # Prevent updates to ESM-only versions + - dependency-name: 'chalk' + versions: ['>=5.0.0'] + - dependency-name: 'execa' + versions: ['>=6.0.0'] + - dependency-name: 'inquirer' + versions: ['>=9.0.0'] + - dependency-name: 'chai' + versions: ['>=5.0.0'] + # Prevent Webpack error caused by v0.11+ of esbuild + # @see https://github.com/dequelabs/axe-core/issues/3771 + - dependency-name: 'esbuild' + versions: ['>=0.11.0'] + groups: + # Any updates not caught by the group config will get individual PRs + npm-low-risk: + update-types: + - 'minor' + - 'patch' diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 49e317eefa..74898ee2d4 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -6,26 +6,21 @@ jobs: prettier: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} - name: Install dependencies run: npm ci - - name: Cache node_modules - uses: actions/cache@v3 - with: - path: | - ./node_modules - key: npm-workspace-cache-${{ runner.os }}-${{ hashFiles('./package-lock.json') }} - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v4 with: node-version: 16 + cache: 'npm' # Workflows are not allowed to edit workflows. As result, we need to prevent Prettier from formatting them. - name: Prevent workflows from being formatted - run: echo ".github" >> .prettierignore + run: echo ".github" >> .prettierignore && cat .prettierignore - run: npm run fmt # Prevent the prettierignore change from being committed. - run: git checkout .prettierignore - - uses: stefanzweifel/git-auto-commit-action@v4 + - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # tag=v5 with: commit_message: ':robot: Automated formatting fixes' diff --git a/.github/workflows/label-extension-linter-issues.yml b/.github/workflows/label-extension-linter-issues.yml index c0505e81f3..fee190e945 100644 --- a/.github/workflows/label-extension-linter-issues.yml +++ b/.github/workflows/label-extension-linter-issues.yml @@ -11,13 +11,13 @@ jobs: issues: write steps: - name: Label Extension Issues - uses: andymckay/labeler@1.0.4 + uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # tag=1 if: contains(toJson(github.event.issue.body), '### Product\n\naxe Extension\n\n') with: add-labels: 'extension' repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Label Linting Issues - uses: andymckay/labeler@1.0.4 + uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # tag=1 if: contains(toJson(github.event.issue.body), '### Product\n\naxe Linter\n\n') with: add-labels: 'linting' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1926d13310..ba0714f1c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,16 +7,13 @@ jobs: name: Create release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/cache@v3 - with: - path: ./node_modules - key: npm-cache-${{ runner.os }}-${{ hashFiles('./package-lock.json') }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 16 + cache: 'npm' - name: Run release script and open PR run: | git config user.name "API Team CI User" @@ -42,11 +39,9 @@ jobs: --output-indicator-new=! CHANGELOG.md | egrep '^!' | awk -F'^[!]' '{print $2}' | sed -e 's/\n/$0A/g' ) - echo "chore(release): v$Version" > /tmp/pr.txt - echo "" >> /tmp/pr.txt echo "$ReleaseNotes" >> /tmp/pr.txt echo "" >> /tmp/pr.txt echo "This PR was opened by a robot :robot: :tada:" >> /tmp/pr.txt - hub pull-request --file /tmp/pr.txt --base master + gh pr create --title "chore(release): v$Version" --body-file "/tmp/pr.txt" --base master env: GITHUB_TOKEN: ${{ secrets.PAT }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..5c5e1cdb25 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: Tests + +on: + pull_request: + push: + branches: + - master + - develop + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - run: npm ci + - run: npm run build + # v4 download seems to have some flakiness with the download of artifacts so pinning to v3 for now + # @see https://github.com/actions/download-artifact/issues/249 + - uses: actions/upload-artifact@v3 + with: + name: axe-core + path: axe.js + retention-days: 1 + + test_node: + strategy: + matrix: + node: [6, 18, 20] + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node}} + - uses: actions/download-artifact@v3 + with: + name: axe-core + - run: npm run test:node diff --git a/.github/workflows/update-generated-files.yaml b/.github/workflows/update-generated-files.yaml index dc2889cdd7..2277e82c6b 100644 --- a/.github/workflows/update-generated-files.yaml +++ b/.github/workflows/update-generated-files.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.husky/pre-commit b/.husky/pre-commit index d57ebf71f2..bce63161fd 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,2 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - npx grunt configure npx lint-staged \ No newline at end of file diff --git a/.jsdoc.json b/.jsdoc.json index 4d7c51274b..df05309b3c 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -12,7 +12,7 @@ "destination": "./doc/api", "encoding": "utf8", "recurse": true, - "template": "./node_modules/minami" + "template": "./node_modules/clean-jsdoc-theme" }, "plugins": ["plugins/markdown"] } diff --git a/.prettierignore b/.prettierignore index 5ae2c1f2b3..2bd1eb1157 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ node_modules/ -doc/api \ No newline at end of file +doc/api diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa5753efc..3523aebf71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [4.9.0](https://github.com/dequelabs/axe-core/compare/v4.8.4...v4.9.0) (2024-03-25) + +### Features + +- adding the wcag131 tag to the aria-hidden-body rule ([#4349](https://github.com/dequelabs/axe-core/issues/4349)) ([dd4c3c3](https://github.com/dequelabs/axe-core/commit/dd4c3c34a42d2b96f5495890f5c5d5e8f6ca8d32)), closes [#4315](https://github.com/dequelabs/axe-core/issues/4315) +- **checks:** deprecate aria-busy check ([#4356](https://github.com/dequelabs/axe-core/issues/4356)) ([be0b555](https://github.com/dequelabs/axe-core/commit/be0b5558acfbeb6bbb176ac7fd7d8fdfb973b30b)), closes [#4347](https://github.com/dequelabs/axe-core/issues/4347) [#4340](https://github.com/dequelabs/axe-core/issues/4340) +- **color:** add color channel values and luminosity, saturation, clip functions ([#4366](https://github.com/dequelabs/axe-core/issues/4366)) ([9e70199](https://github.com/dequelabs/axe-core/commit/9e7019990bbbf5182ab50c5c968143b81d216dcb)), closes [/github.com/dequelabs/axe-core/pull/4365/files#r1517706612](https://github.com/dequelabs//github.com/dequelabs/axe-core/pull/4365/files/issues/r1517706612) +- **i18n:** add Greek Translations ([#3836](https://github.com/dequelabs/axe-core/issues/3836)) ([3ea9a48](https://github.com/dequelabs/axe-core/commit/3ea9a48cf88d02271db8b19651bff0415237b856)) +- **i18n:** Add Italian translation ([#4344](https://github.com/dequelabs/axe-core/issues/4344)) ([de1baa9](https://github.com/dequelabs/axe-core/commit/de1baa9a9f6495f695d25d61d14ed55983dded76)) +- **i18n:** Add Simplified Chinese translation ([#4379](https://github.com/dequelabs/axe-core/issues/4379)) ([bda7c8d](https://github.com/dequelabs/axe-core/commit/bda7c8d8bf5936a56c66240e1ea0373a3b769809)) +- **i18n:** Add Taiwanese Mandarin translation ([#4299](https://github.com/dequelabs/axe-core/issues/4299)) ([c5e11de](https://github.com/dequelabs/axe-core/commit/c5e11de06973392b113906c05e3a3004af4c38ae)) + +### Bug Fixes + +- Add LICENSE-3RD-PARTY.txt file ([#4304](https://github.com/dequelabs/axe-core/issues/4304)) ([daa0fe6](https://github.com/dequelabs/axe-core/commit/daa0fe677d4837c9c79bad8ee6c77aff11212339)) +- add Object.values polyfill for node <=6 ([#4274](https://github.com/dequelabs/axe-core/issues/4274)) ([5eb867b](https://github.com/dequelabs/axe-core/commit/5eb867b04e174140122c62eb5c705a842a3489e1)) +- **aria-required-children:** avoid confusing aria-busy message in failures ([#4347](https://github.com/dequelabs/axe-core/issues/4347)) ([591607d](https://github.com/dequelabs/axe-core/commit/591607dd829c11e2cca5beee12c75628d1a8235e)), closes [#fail13](https://github.com/dequelabs/axe-core/issues/fail13) [#4340](https://github.com/dequelabs/axe-core/issues/4340) +- avoid reading element-specific node properties of non-element node types ([#4317](https://github.com/dequelabs/axe-core/issues/4317)) ([b853b18](https://github.com/dequelabs/axe-core/commit/b853b18a24dd2d1c9408705b821cc11146ae1186)), closes [#4316](https://github.com/dequelabs/axe-core/issues/4316) [#4316](https://github.com/dequelabs/axe-core/issues/4316) +- **color-contrast:** handle text that is outside `overflow: hidden` ancestor ([#4357](https://github.com/dequelabs/axe-core/issues/4357)) ([bdb7300](https://github.com/dequelabs/axe-core/commit/bdb7300c67d451d3b0169707924a0c6bc4defe40)), closes [#4253](https://github.com/dequelabs/axe-core/issues/4253) +- **color-contrast:** support color blend modes hue, saturation, color, luminosity ([#4365](https://github.com/dequelabs/axe-core/issues/4365)) ([7ae4761](https://github.com/dequelabs/axe-core/commit/7ae476124d60eafd28d85abf48188cd85c99543a)) +- **d.ts:** RawNodesResult issues ([#4229](https://github.com/dequelabs/axe-core/issues/4229)) ([d660518](https://github.com/dequelabs/axe-core/commit/d6605181ec942bcca46e3bfe889064b3781919ca)) +- **d.ts:** RunOptions.reporter can be any string ([#4218](https://github.com/dequelabs/axe-core/issues/4218)) ([e53f5c5](https://github.com/dequelabs/axe-core/commit/e53f5c5184a0e5f75db65e7929a9da84d7ee6df6)) +- **i18n:** update Italian translations ([#4377](https://github.com/dequelabs/axe-core/issues/4377)) ([4d65d4b](https://github.com/dequelabs/axe-core/commit/4d65d4bf40f4ee2697e079451dd84a0155e8fb51)) +- **listitem:** clarify roleNotValid message ([#4374](https://github.com/dequelabs/axe-core/issues/4374)) ([0f8a9af](https://github.com/dequelabs/axe-core/commit/0f8a9af2a82d49e7d8ff3024da0e4c485ca46d38)) +- **scrollable-region-focusable:** missing wcag213 tag ([#4201](https://github.com/dequelabs/axe-core/issues/4201)) ([0080a72](https://github.com/dequelabs/axe-core/commit/0080a7255eb7f246bb7b6f53974a95b65983b83a)) +- **target-size:** always pass 10x targets (avoid perf bottleneck) ([#4376](https://github.com/dequelabs/axe-core/issues/4376)) ([be327c4](https://github.com/dequelabs/axe-core/commit/be327c422f67ac657218f711b3b799567ba3aa37)) +- **target-size:** do not crash for nodes with many overlapping widgets ([#4373](https://github.com/dequelabs/axe-core/issues/4373)) ([1dbea83](https://github.com/dequelabs/axe-core/commit/1dbea83d4749f9f71f263883869b076b0d42021f)), closes [#4359](https://github.com/dequelabs/axe-core/issues/4359) [#4359](https://github.com/dequelabs/axe-core/issues/4359) [#4360](https://github.com/dequelabs/axe-core/issues/4360) +- **utils/get-selector:** ignore 'xmlns' attribute when generating a selector ([#4303](https://github.com/dequelabs/axe-core/issues/4303)) ([938b411](https://github.com/dequelabs/axe-core/commit/938b411bb0609b54e5c46a8e5b50c9ea4de4bdee)) + ### [4.8.4](https://github.com/dequelabs/axe-core/compare/v4.8.3...v4.8.4) (2024-02-07) ### Bug Fixes diff --git a/README.md b/README.md index 5697270b10..1f6ccfec47 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ The [axe-core API](doc/API.md) package consists of: ## Localization -Axe can be built using your local language. To do so, a localization file must be added to the `./locales` directory. This file must have be named in the following manner: `.json`. To build axe using this locale, instead of the default, run axe with the `--lang` flag, like so: +Axe can be built using your local language. To do so, a localization file must be added to the `./locales` directory. This file must be named in the following manner: `.json`. To build axe using this locale, instead of the default, run axe with the `--lang` flag, like so: `grunt build --lang=nl` @@ -101,9 +101,9 @@ or equivalently: `npm run build -- --lang=nl` -This will create a new build for axe, called `axe..js` and `axe..min.js`. If you want to build localized versions, simply pass in `--all-lang` instead. If you want to build multiple localized versions (but not all of them), you can pass in a comma-separated list of languages to the `--lang` flag, like `--lang=nl,ja`. +This will create a new build for axe, called `axe..js` and `axe..min.js`. If you want to build all localized versions, simply pass in `--all-lang` instead. If you want to build multiple localized versions (but not all of them), you can pass in a comma-separated list of languages to the `--lang` flag, like `--lang=nl,ja`. -To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/docs/check-message-template.md). +To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/doc/check-message-template.md). To update existing translation file, re-run `grunt translate --lang=`. This will add new messages used in English and remove messages which were not used in English. @@ -149,6 +149,10 @@ Axe-core supports the following locales. Do note that since locales are contribu - Polish - Portuguese (Brazilian) - Spanish +- Greek +- Italian +- Simplified Chinese +- Traditional Chinese ## Updates & Security diff --git a/bower.json b/bower.json index 27a80bd8c1..bff620abe3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.8.4", + "version": "4.9.0", "deprecated": true, "contributors": [ { diff --git a/build/check-node-version.js b/build/check-node-version.js index b56cf0abdc..53f7cce71c 100644 --- a/build/check-node-version.js +++ b/build/check-node-version.js @@ -2,7 +2,7 @@ const currentVersion = process.version.replace('v', ''); -const minimumVersionMajor = 12; +const minimumVersionMajor = 18; const currentVersionMajor = parseInt(currentVersion.split('.')[0]); const usesMinimumVersion = currentVersionMajor >= minimumVersionMajor; diff --git a/build/configure.js b/build/configure.js index d4998985e5..ca3241fbd3 100644 --- a/build/configure.js +++ b/build/configure.js @@ -6,7 +6,7 @@ var clone = require('clone'); var doT = require('@deque/dot'); var templates = require('./templates'); var buildManual = require('./build-manual'); -var entities = new (require('html-entities').AllHtmlEntities)(); +var { encode } = require('html-entities'); var packageJSON = require('../package.json'); var doTRegex = /\{\{.+?\}\}/g; @@ -365,7 +365,7 @@ function buildRules(grunt, options, commons, callback) { result.push([ `[${rule.id}](https://dequeuniversity.com/rules/axe/${axeVersion}/${rule.id}?application=RuleDescription)`, - entities.encode(rule.metadata.description), + encode(rule.metadata.description), impact, rule.tags.join(', '), issueType.join(', '), @@ -401,6 +401,8 @@ ${ .join('\n\n'); var descriptions = ` + + # Rule Descriptions ## Table of Contents diff --git a/build/rule-generator/questions.js b/build/rule-generator/questions.js index 35d5fcfaba..dc5e665d0c 100644 --- a/build/rule-generator/questions.js +++ b/build/rule-generator/questions.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const globby = require('globby'); +const { glob } = require('glob'); const directories = require('./directories'); /** @@ -33,9 +33,7 @@ const validateGetRuleName = async input => { throw new Error(`RULE name conflicts with an existing rule's filename.`); } // 3) ensure no rule id overlaps - const ruleSpecs = await globby(directories.rules, { - expandDirectories: { extensions: ['json'] } - }); + const ruleSpecs = await glob(`${directories.rules}/**/*.json`); const axeRulesIds = ruleSpecs.reduce((out, specPath) => { const spec = require(specPath); out.push(spec.id); @@ -64,9 +62,7 @@ const validateGetCheckName = async input => { ); } // 2) ensure no check filename overlaps - const checkSpecs = await globby(directories.checks, { - expandDirectories: { extensions: ['json'] } - }); + const checkSpecs = await glob(`${directories.checks}/**/*.json`); // cannot use `fs.existsSync` here, as we do not know which category of checks to look under const axeChecksFileNames = checkSpecs.map( f => f.replace('.json', '').split('/').reverse()[0] @@ -75,9 +71,7 @@ const validateGetCheckName = async input => { throw new Error('CHECK name conflicts with an existing filename.'); } // 3) ensure no check id overlaps - const ruleSpecs = await globby(directories.rules, { - expandDirectories: { extensions: ['json'] } - }); + const ruleSpecs = await glob(`${directories.rules}/**/*.json`); const axe = require(directories.axePath); const axeChecksIds = ruleSpecs.reduce((out, specPath) => { const spec = require(specPath); diff --git a/build/shared/create-file.js b/build/shared/create-file.js index d5faccff3d..ea459da273 100644 --- a/build/shared/create-file.js +++ b/build/shared/create-file.js @@ -1,8 +1,5 @@ -const fs = require('fs'); -const { promisify } = require('util'); -const { dirname: getDirName } = require('path'); -const makeDir = require('make-dir'); -const writeFile = promisify(fs.writeFile); +const { promises: fs } = require('fs'); +const { dirname } = require('path'); /** * Create file with given contents at specified location @@ -12,6 +9,8 @@ const writeFile = promisify(fs.writeFile); * @returns {Promise} */ const createFile = (path, content) => - makeDir(getDirName(path)).then(() => writeFile(path, content)); + fs + .mkdir(dirname(path), { recursive: true }) + .then(() => fs.writeFile(path, content)); module.exports = createFile; diff --git a/build/tasks/aria-supported.js b/build/tasks/aria-supported.js index 5f76ca2b16..257b52ed53 100644 --- a/build/tasks/aria-supported.js +++ b/build/tasks/aria-supported.js @@ -2,7 +2,6 @@ 'use strict'; const { roles, aria: props } = require('aria-query'); -const mdTable = require('markdown-table'); const format = require('../shared/format'); module.exports = function (grunt) { @@ -16,6 +15,7 @@ module.exports = function (grunt) { * as `axe` does not exist until grunt task `build:uglify` is complete, * hence cannot be required at the top of the file. */ + const done = this.async(); const { langs } = this.options(); const fileNameSuffix = langs && langs.length > 0 ? `${langs[0]}` : ''; const axe = require(`../../axe${fileNameSuffix}`); @@ -50,10 +50,16 @@ module.exports = function (grunt) { ariaAttrs, listType ); - const attributesTableMarkdown = mdTable([ + const formatMarkdownTableRow = columnValues => + `| ${columnValues.join(' | ')} |`; + const attributesTableWithHeader = [ headings.attributesMdTableHeader, + ['---', '---'], ...attributesTable - ]); + ]; + const attributesTableMarkdown = attributesTableWithHeader + .map(formatMarkdownTableRow) + .join('\n'); const footnotes = [...rolesFootnotes, ...attributesFootnotes].map( (footnote, index) => `[^${index + 1}]: ${footnote}` @@ -64,10 +70,16 @@ module.exports = function (grunt) { const destFile = this.data.destFile; // Format the content so Prettier doesn't create a diff after running. // See https://github.com/dequelabs/axe-core/issues/1310. - const formattedContent = format(content, destFile); - - // write `aria supported` file contents - grunt.file.write(destFile, formattedContent); + format(content, destFile) + .then(formattedContent => { + // write `aria supported` file contents + grunt.file.write(destFile, formattedContent); + done(); + }) + .catch(err => { + console.error(err.message); + done(false); + }); /** * Get list of aria attributes, from `aria-query` diff --git a/build/tasks/configure.js b/build/tasks/configure.js index 86599e9951..f4a3c6d262 100644 --- a/build/tasks/configure.js +++ b/build/tasks/configure.js @@ -1,15 +1,15 @@ /*eslint-env node */ 'use strict'; -var buildRules = require('../configure'); -var format = require('../shared/format'); +const buildRules = require('../configure'); +const format = require('../shared/format'); module.exports = function (grunt) { grunt.registerMultiTask( 'configure', 'Task for configuring rules and checks', function () { - var done = this.async(); - var options = this.options({ + const done = this.async(); + const options = this.options({ rules: ['lib/rules/**/*.json'], checks: ['lib/checks/**/*.json'], misc: ['lib/misc/**/*.json'], @@ -18,7 +18,7 @@ module.exports = function (grunt) { }); this.files.forEach(function (file) { - var match = file.dest.auto.match(/\.([a-z]{2,3})\.js/); + const match = file.dest.auto.match(/\.([a-z]{2,3})\.js/); if (match) { options.locale = match[1]; } @@ -28,12 +28,15 @@ module.exports = function (grunt) { // Format the content so Prettier doesn't create a diff after running. // See https://github.com/dequelabs/axe-core/issues/1310. - const descriptionsContent = format( - result.descriptions, - file.dest.descriptions - ); - grunt.file.write(file.dest.descriptions, descriptionsContent); - done(); + format(result.descriptions, file.dest.descriptions) + .then(descriptionsContent => { + grunt.file.write(file.dest.descriptions, descriptionsContent); + done(); + }) + .catch(err => { + console.error(err.message); + done(false); + }); }); }); } diff --git a/doc/API.md b/doc/API.md index 5f916713b4..ea1f04a447 100644 --- a/doc/API.md +++ b/doc/API.md @@ -670,7 +670,7 @@ The results of axe are grouped according to their outcome into the following arr - `passes`: These results indicate what elements passed the rules - `violations`: These results indicate what elements failed the rules - `inapplicable`: These results indicate which rules did not run because no matching content was found on the page. For example, with no video, those rules won't run. -- `incomplete`: These results were aborted and require further testing. This can happen either because of technical restrictions to what the rule can test, or because a javascript error occurred. +- `incomplete`: Also known as "needs review," these results were aborted and require further testing. This can happen either because of technical restrictions to what the rule can test, or because a javascript error occurred. Each object returned in these arrays have the following properties: diff --git a/doc/examples/qunit/test/test.html b/doc/examples/qunit/test/test.html index 22e013e77b..e7a3acbb5c 100644 --- a/doc/examples/qunit/test/test.html +++ b/doc/examples/qunit/test/test.html @@ -1,4 +1,4 @@ - + diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index bf03843516..75787363d5 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -1,3 +1,5 @@ + + # Rule Descriptions ## Table of Contents @@ -14,71 +16,71 @@ | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [area-alt](https://dequeuniversity.com/rules/axe/4.8/area-alt?application=RuleDescription) | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | -| [aria-allowed-attr](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-attr?application=RuleDescription) | Ensures an element's role supports its ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-braille-equivalent](https://dequeuniversity.com/rules/axe/4.8/aria-braille-equivalent?application=RuleDescription) | Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | | -| [aria-command-name](https://dequeuniversity.com/rules/axe/4.8/aria-command-name?application=RuleDescription) | Ensures every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | -| [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.8/aria-conditional-attr?application=RuleDescription) | Ensures ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.8/aria-deprecated-role?application=RuleDescription) | Ensures elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | -| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.8/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | | -| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.8/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | -| [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.8/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [aria-meter-name](https://dequeuniversity.com/rules/axe/4.8/aria-meter-name?application=RuleDescription) | Ensures every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | -| [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.8/aria-progressbar-name?application=RuleDescription) | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | -| [aria-prohibited-attr](https://dequeuniversity.com/rules/axe/4.8/aria-prohibited-attr?application=RuleDescription) | Ensures ARIA attributes are not prohibited for an element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | -| [aria-required-attr](https://dequeuniversity.com/rules/axe/4.8/aria-required-attr?application=RuleDescription) | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) | -| [aria-required-children](https://dequeuniversity.com/rules/axe/4.8/aria-required-children?application=RuleDescription) | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75), [ff89c9](https://act-rules.github.io/rules/ff89c9) | -| [aria-required-parent](https://dequeuniversity.com/rules/axe/4.8/aria-required-parent?application=RuleDescription) | Ensures elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) | -| [aria-roles](https://dequeuniversity.com/rules/axe/4.8/aria-roles?application=RuleDescription) | Ensures all elements with a role attribute use a valid value | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | -| [aria-toggle-field-name](https://dequeuniversity.com/rules/axe/4.8/aria-toggle-field-name?application=RuleDescription) | Ensures every ARIA toggle field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [aria-tooltip-name](https://dequeuniversity.com/rules/axe/4.8/aria-tooltip-name?application=RuleDescription) | Ensures every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | | -| [aria-valid-attr-value](https://dequeuniversity.com/rules/axe/4.8/aria-valid-attr-value?application=RuleDescription) | Ensures all ARIA attributes have valid values | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | -| [aria-valid-attr](https://dequeuniversity.com/rules/axe/4.8/aria-valid-attr?application=RuleDescription) | Ensures attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5f99a7](https://act-rules.github.io/rules/5f99a7) | -| [blink](https://dequeuniversity.com/rules/axe/4.8/blink?application=RuleDescription) | Ensures <blink> elements are not used | Serious | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | -| [button-name](https://dequeuniversity.com/rules/axe/4.8/button-name?application=RuleDescription) | Ensures buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1), [m6b1q3](https://act-rules.github.io/rules/m6b1q3) | -| [bypass](https://dequeuniversity.com/rules/axe/4.8/bypass?application=RuleDescription) | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o, TTv5, TT9.a, EN-301-549, EN-9.2.4.1 | needs review | [cf77f2](https://act-rules.github.io/rules/cf77f2), [047fe0](https://act-rules.github.io/rules/047fe0), [b40fd1](https://act-rules.github.io/rules/b40fd1), [3e12e1](https://act-rules.github.io/rules/3e12e1), [ye5d6e](https://act-rules.github.io/rules/ye5d6e) | -| [color-contrast](https://dequeuniversity.com/rules/axe/4.8/color-contrast?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143, TTv5, TT13.c, EN-301-549, EN-9.1.4.3, ACT | failure, needs review | [afw4f7](https://act-rules.github.io/rules/afw4f7), [09o5cg](https://act-rules.github.io/rules/09o5cg) | -| [definition-list](https://dequeuniversity.com/rules/axe/4.8/definition-list?application=RuleDescription) | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [dlitem](https://dequeuniversity.com/rules/axe/4.8/dlitem?application=RuleDescription) | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [document-title](https://dequeuniversity.com/rules/axe/4.8/document-title?application=RuleDescription) | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242, TTv5, TT12.a, EN-301-549, EN-9.2.4.2, ACT | failure | [2779a5](https://act-rules.github.io/rules/2779a5) | -| [duplicate-id-aria](https://dequeuniversity.com/rules/axe/4.8/duplicate-id-aria?application=RuleDescription) | Ensures every id attribute value used in ARIA and in labels is unique | Critical | cat.parsing, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | -| [form-field-multiple-labels](https://dequeuniversity.com/rules/axe/4.8/form-field-multiple-labels?application=RuleDescription) | Ensures form field does not have multiple label elements | Moderate | cat.forms, wcag2a, wcag332, TTv5, TT5.c, EN-301-549, EN-9.3.3.2 | needs review | | -| [frame-focusable-content](https://dequeuniversity.com/rules/axe/4.8/frame-focusable-content?application=RuleDescription) | Ensures <frame> and <iframe> elements with focusable content do not have tabindex=-1 | Serious | cat.keyboard, wcag2a, wcag211, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | failure, needs review | [akn7bn](https://act-rules.github.io/rules/akn7bn) | -| [frame-title-unique](https://dequeuniversity.com/rules/axe/4.8/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, wcag2a, wcag412, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | needs review | [4b1c6c](https://act-rules.github.io/rules/4b1c6c) | -| [frame-title](https://dequeuniversity.com/rules/axe/4.8/frame-title?application=RuleDescription) | Ensures <iframe> and <frame> elements have an accessible name | Serious | cat.text-alternatives, wcag2a, wcag412, section508, section508.22.i, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | failure, needs review | [cae760](https://act-rules.github.io/rules/cae760) | -| [html-has-lang](https://dequeuniversity.com/rules/axe/4.8/html-has-lang?application=RuleDescription) | Ensures every HTML document has a lang attribute | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [b5c3f8](https://act-rules.github.io/rules/b5c3f8) | -| [html-lang-valid](https://dequeuniversity.com/rules/axe/4.8/html-lang-valid?application=RuleDescription) | Ensures the lang attribute of the <html> element has a valid value | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [bf051a](https://act-rules.github.io/rules/bf051a) | -| [html-xml-lang-mismatch](https://dequeuniversity.com/rules/axe/4.8/html-xml-lang-mismatch?application=RuleDescription) | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page | Moderate | cat.language, wcag2a, wcag311, EN-301-549, EN-9.3.1.1, ACT | failure | [5b7ae0](https://act-rules.github.io/rules/5b7ae0) | -| [image-alt](https://dequeuniversity.com/rules/axe/4.8/image-alt?application=RuleDescription) | Ensures <img> elements have alternate text or a role of none or presentation | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, TT7.b, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | -| [input-button-name](https://dequeuniversity.com/rules/axe/4.8/input-button-name?application=RuleDescription) | Ensures input buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | -| [input-image-alt](https://dequeuniversity.com/rules/axe/4.8/input-image-alt?application=RuleDescription) | Ensures <input type="image"> elements have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, wcag412, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, EN-9.4.1.2, ACT | failure, needs review | [59796f](https://act-rules.github.io/rules/59796f) | -| [label](https://dequeuniversity.com/rules/axe/4.8/label?application=RuleDescription) | Ensures every form element has a label | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [link-in-text-block](https://dequeuniversity.com/rules/axe/4.8/link-in-text-block?application=RuleDescription) | Ensure links are distinguished from surrounding text in a way that does not rely on color | Serious | cat.color, wcag2a, wcag141, TTv5, TT13.a, EN-301-549, EN-9.1.4.1 | failure, needs review | | -| [link-name](https://dequeuniversity.com/rules/axe/4.8/link-name?application=RuleDescription) | Ensures links have discernible text | Serious | cat.name-role-value, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | -| [list](https://dequeuniversity.com/rules/axe/4.8/list?application=RuleDescription) | Ensures that lists are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [listitem](https://dequeuniversity.com/rules/axe/4.8/listitem?application=RuleDescription) | Ensures <li> elements are used semantically | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | -| [marquee](https://dequeuniversity.com/rules/axe/4.8/marquee?application=RuleDescription) | Ensures <marquee> elements are not used | Serious | cat.parsing, wcag2a, wcag222, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | -| [meta-refresh](https://dequeuniversity.com/rules/axe/4.8/meta-refresh?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Critical | cat.time-and-media, wcag2a, wcag221, TTv5, TT8.a, EN-301-549, EN-9.2.2.1 | failure | [bc659a](https://act-rules.github.io/rules/bc659a), [bisz58](https://act-rules.github.io/rules/bisz58) | -| [meta-viewport](https://dequeuniversity.com/rules/axe/4.8/meta-viewport?application=RuleDescription) | Ensures <meta name="viewport"> does not disable text scaling and zooming | Critical | cat.sensory-and-visual-cues, wcag2aa, wcag144, EN-301-549, EN-9.1.4.4, ACT | failure | [b4f0c3](https://act-rules.github.io/rules/b4f0c3) | -| [nested-interactive](https://dequeuniversity.com/rules/axe/4.8/nested-interactive?application=RuleDescription) | Ensures interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies | Serious | cat.keyboard, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [307n5z](https://act-rules.github.io/rules/307n5z) | -| [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.8/no-autoplay-audio?application=RuleDescription) | Ensures <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, TTv5, TT2.a, EN-301-549, EN-9.1.4.2, ACT | needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | -| [object-alt](https://dequeuniversity.com/rules/axe/4.8/object-alt?application=RuleDescription) | Ensures <object> elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, EN-301-549, EN-9.1.1.1 | failure, needs review | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | -| [role-img-alt](https://dequeuniversity.com/rules/axe/4.8/role-img-alt?application=RuleDescription) | Ensures [role="img"] elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | -| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.8/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard | Serious | cat.keyboard, wcag2a, wcag211, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | -| [select-name](https://dequeuniversity.com/rules/axe/4.8/select-name?application=RuleDescription) | Ensures select element has an accessible name | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| [server-side-image-map](https://dequeuniversity.com/rules/axe/4.8/server-side-image-map?application=RuleDescription) | Ensures that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | needs review | | -| [svg-img-alt](https://dequeuniversity.com/rules/axe/4.8/svg-img-alt?application=RuleDescription) | Ensures <svg> elements with an img, graphics-document or graphics-symbol role have an accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [7d6734](https://act-rules.github.io/rules/7d6734) | -| [td-headers-attr](https://dequeuniversity.com/rules/axe/4.8/td-headers-attr?application=RuleDescription) | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [a25f45](https://act-rules.github.io/rules/a25f45) | -| [th-has-data-cells](https://dequeuniversity.com/rules/axe/4.8/th-has-data-cells?application=RuleDescription) | Ensure that <th> elements and elements with role=columnheader/rowheader have data cells they describe | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [d0f69e](https://act-rules.github.io/rules/d0f69e) | -| [valid-lang](https://dequeuniversity.com/rules/axe/4.8/valid-lang?application=RuleDescription) | Ensures lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | -| [video-caption](https://dequeuniversity.com/rules/axe/4.8/video-caption?application=RuleDescription) | Ensures <video> elements have captions | Critical | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a, TTv5, TT17.a, EN-301-549, EN-9.1.2.2 | needs review | [eac66b](https://act-rules.github.io/rules/eac66b) | +| [area-alt](https://dequeuniversity.com/rules/axe/4.9/area-alt?application=RuleDescription) | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | +| [aria-allowed-attr](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-attr?application=RuleDescription) | Ensures an element's role supports its ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-braille-equivalent](https://dequeuniversity.com/rules/axe/4.9/aria-braille-equivalent?application=RuleDescription) | Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | | +| [aria-command-name](https://dequeuniversity.com/rules/axe/4.9/aria-command-name?application=RuleDescription) | Ensures every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | +| [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.9/aria-conditional-attr?application=RuleDescription) | Ensures ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.9/aria-deprecated-role?application=RuleDescription) | Ensures elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | +| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.9/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2 | failure | | +| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.9/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | +| [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.9/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [aria-meter-name](https://dequeuniversity.com/rules/axe/4.9/aria-meter-name?application=RuleDescription) | Ensures every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | +| [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.9/aria-progressbar-name?application=RuleDescription) | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | | +| [aria-prohibited-attr](https://dequeuniversity.com/rules/axe/4.9/aria-prohibited-attr?application=RuleDescription) | Ensures ARIA attributes are not prohibited for an element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [5c01ea](https://act-rules.github.io/rules/5c01ea) | +| [aria-required-attr](https://dequeuniversity.com/rules/axe/4.9/aria-required-attr?application=RuleDescription) | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) | +| [aria-required-children](https://dequeuniversity.com/rules/axe/4.9/aria-required-children?application=RuleDescription) | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75), [ff89c9](https://act-rules.github.io/rules/ff89c9) | +| [aria-required-parent](https://dequeuniversity.com/rules/axe/4.9/aria-required-parent?application=RuleDescription) | Ensures elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) | +| [aria-roles](https://dequeuniversity.com/rules/axe/4.9/aria-roles?application=RuleDescription) | Ensures all elements with a role attribute use a valid value | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) | +| [aria-toggle-field-name](https://dequeuniversity.com/rules/axe/4.9/aria-toggle-field-name?application=RuleDescription) | Ensures every ARIA toggle field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [aria-tooltip-name](https://dequeuniversity.com/rules/axe/4.9/aria-tooltip-name?application=RuleDescription) | Ensures every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | | +| [aria-valid-attr-value](https://dequeuniversity.com/rules/axe/4.9/aria-valid-attr-value?application=RuleDescription) | Ensures all ARIA attributes have valid values | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | +| [aria-valid-attr](https://dequeuniversity.com/rules/axe/4.9/aria-valid-attr?application=RuleDescription) | Ensures attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5f99a7](https://act-rules.github.io/rules/5f99a7) | +| [blink](https://dequeuniversity.com/rules/axe/4.9/blink?application=RuleDescription) | Ensures <blink> elements are not used | Serious | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | +| [button-name](https://dequeuniversity.com/rules/axe/4.9/button-name?application=RuleDescription) | Ensures buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1), [m6b1q3](https://act-rules.github.io/rules/m6b1q3) | +| [bypass](https://dequeuniversity.com/rules/axe/4.9/bypass?application=RuleDescription) | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o, TTv5, TT9.a, EN-301-549, EN-9.2.4.1 | needs review | [cf77f2](https://act-rules.github.io/rules/cf77f2), [047fe0](https://act-rules.github.io/rules/047fe0), [b40fd1](https://act-rules.github.io/rules/b40fd1), [3e12e1](https://act-rules.github.io/rules/3e12e1), [ye5d6e](https://act-rules.github.io/rules/ye5d6e) | +| [color-contrast](https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143, TTv5, TT13.c, EN-301-549, EN-9.1.4.3, ACT | failure, needs review | [afw4f7](https://act-rules.github.io/rules/afw4f7), [09o5cg](https://act-rules.github.io/rules/09o5cg) | +| [definition-list](https://dequeuniversity.com/rules/axe/4.9/definition-list?application=RuleDescription) | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [dlitem](https://dequeuniversity.com/rules/axe/4.9/dlitem?application=RuleDescription) | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [document-title](https://dequeuniversity.com/rules/axe/4.9/document-title?application=RuleDescription) | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242, TTv5, TT12.a, EN-301-549, EN-9.2.4.2, ACT | failure | [2779a5](https://act-rules.github.io/rules/2779a5) | +| [duplicate-id-aria](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-aria?application=RuleDescription) | Ensures every id attribute value used in ARIA and in labels is unique | Critical | cat.parsing, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | needs review | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [form-field-multiple-labels](https://dequeuniversity.com/rules/axe/4.9/form-field-multiple-labels?application=RuleDescription) | Ensures form field does not have multiple label elements | Moderate | cat.forms, wcag2a, wcag332, TTv5, TT5.c, EN-301-549, EN-9.3.3.2 | needs review | | +| [frame-focusable-content](https://dequeuniversity.com/rules/axe/4.9/frame-focusable-content?application=RuleDescription) | Ensures <frame> and <iframe> elements with focusable content do not have tabindex=-1 | Serious | cat.keyboard, wcag2a, wcag211, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | failure, needs review | [akn7bn](https://act-rules.github.io/rules/akn7bn) | +| [frame-title-unique](https://dequeuniversity.com/rules/axe/4.9/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, wcag2a, wcag412, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | needs review | [4b1c6c](https://act-rules.github.io/rules/4b1c6c) | +| [frame-title](https://dequeuniversity.com/rules/axe/4.9/frame-title?application=RuleDescription) | Ensures <iframe> and <frame> elements have an accessible name | Serious | cat.text-alternatives, wcag2a, wcag412, section508, section508.22.i, TTv5, TT12.d, EN-301-549, EN-9.4.1.2 | failure, needs review | [cae760](https://act-rules.github.io/rules/cae760) | +| [html-has-lang](https://dequeuniversity.com/rules/axe/4.9/html-has-lang?application=RuleDescription) | Ensures every HTML document has a lang attribute | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [b5c3f8](https://act-rules.github.io/rules/b5c3f8) | +| [html-lang-valid](https://dequeuniversity.com/rules/axe/4.9/html-lang-valid?application=RuleDescription) | Ensures the lang attribute of the <html> element has a valid value | Serious | cat.language, wcag2a, wcag311, TTv5, TT11.a, EN-301-549, EN-9.3.1.1, ACT | failure | [bf051a](https://act-rules.github.io/rules/bf051a) | +| [html-xml-lang-mismatch](https://dequeuniversity.com/rules/axe/4.9/html-xml-lang-mismatch?application=RuleDescription) | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page | Moderate | cat.language, wcag2a, wcag311, EN-301-549, EN-9.3.1.1, ACT | failure | [5b7ae0](https://act-rules.github.io/rules/5b7ae0) | +| [image-alt](https://dequeuniversity.com/rules/axe/4.9/image-alt?application=RuleDescription) | Ensures <img> elements have alternate text or a role of none or presentation | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, TT7.b, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | +| [input-button-name](https://dequeuniversity.com/rules/axe/4.9/input-button-name?application=RuleDescription) | Ensures input buttons have discernible text | Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | +| [input-image-alt](https://dequeuniversity.com/rules/axe/4.9/input-image-alt?application=RuleDescription) | Ensures <input type="image"> elements have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, wcag412, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, EN-9.4.1.2, ACT | failure, needs review | [59796f](https://act-rules.github.io/rules/59796f) | +| [label](https://dequeuniversity.com/rules/axe/4.9/label?application=RuleDescription) | Ensures every form element has a label | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [link-in-text-block](https://dequeuniversity.com/rules/axe/4.9/link-in-text-block?application=RuleDescription) | Ensure links are distinguished from surrounding text in a way that does not rely on color | Serious | cat.color, wcag2a, wcag141, TTv5, TT13.a, EN-301-549, EN-9.1.4.1 | failure, needs review | | +| [link-name](https://dequeuniversity.com/rules/axe/4.9/link-name?application=RuleDescription) | Ensures links have discernible text | Serious | cat.name-role-value, wcag2a, wcag244, wcag412, section508, section508.22.a, TTv5, TT6.a, EN-301-549, EN-9.2.4.4, EN-9.4.1.2, ACT | failure, needs review | [c487ae](https://act-rules.github.io/rules/c487ae) | +| [list](https://dequeuniversity.com/rules/axe/4.9/list?application=RuleDescription) | Ensures that lists are structured correctly | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [listitem](https://dequeuniversity.com/rules/axe/4.9/listitem?application=RuleDescription) | Ensures <li> elements are used semantically | Serious | cat.structure, wcag2a, wcag131, EN-301-549, EN-9.1.3.1 | failure | | +| [marquee](https://dequeuniversity.com/rules/axe/4.9/marquee?application=RuleDescription) | Ensures <marquee> elements are not used | Serious | cat.parsing, wcag2a, wcag222, TTv5, TT2.b, EN-301-549, EN-9.2.2.2 | failure | | +| [meta-refresh](https://dequeuniversity.com/rules/axe/4.9/meta-refresh?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Critical | cat.time-and-media, wcag2a, wcag221, TTv5, TT8.a, EN-301-549, EN-9.2.2.1 | failure | [bc659a](https://act-rules.github.io/rules/bc659a), [bisz58](https://act-rules.github.io/rules/bisz58) | +| [meta-viewport](https://dequeuniversity.com/rules/axe/4.9/meta-viewport?application=RuleDescription) | Ensures <meta name="viewport"> does not disable text scaling and zooming | Critical | cat.sensory-and-visual-cues, wcag2aa, wcag144, EN-301-549, EN-9.1.4.4, ACT | failure | [b4f0c3](https://act-rules.github.io/rules/b4f0c3) | +| [nested-interactive](https://dequeuniversity.com/rules/axe/4.9/nested-interactive?application=RuleDescription) | Ensures interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies | Serious | cat.keyboard, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [307n5z](https://act-rules.github.io/rules/307n5z) | +| [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.9/no-autoplay-audio?application=RuleDescription) | Ensures <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, TTv5, TT2.a, EN-301-549, EN-9.1.4.2, ACT | needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | +| [object-alt](https://dequeuniversity.com/rules/axe/4.9/object-alt?application=RuleDescription) | Ensures <object> elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, EN-301-549, EN-9.1.1.1 | failure, needs review | [8fc3b6](https://act-rules.github.io/rules/8fc3b6) | +| [role-img-alt](https://dequeuniversity.com/rules/axe/4.9/role-img-alt?application=RuleDescription) | Ensures [role="img"] elements have alternate text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [23a2a8](https://act-rules.github.io/rules/23a2a8) | +| [scrollable-region-focusable](https://dequeuniversity.com/rules/axe/4.9/scrollable-region-focusable?application=RuleDescription) | Ensure elements that have scrollable content are accessible by keyboard | Serious | cat.keyboard, wcag2a, wcag211, wcag213, TTv5, TT4.a, EN-301-549, EN-9.2.1.1, EN-9.2.1.3 | failure | [0ssw9k](https://act-rules.github.io/rules/0ssw9k) | +| [select-name](https://dequeuniversity.com/rules/axe/4.9/select-name?application=RuleDescription) | Ensures select element has an accessible name | Critical | cat.forms, wcag2a, wcag412, section508, section508.22.n, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | +| [server-side-image-map](https://dequeuniversity.com/rules/axe/4.9/server-side-image-map?application=RuleDescription) | Ensures that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f, TTv5, TT4.a, EN-301-549, EN-9.2.1.1 | needs review | | +| [svg-img-alt](https://dequeuniversity.com/rules/axe/4.9/svg-img-alt?application=RuleDescription) | Ensures <svg> elements with an img, graphics-document or graphics-symbol role have an accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT | failure, needs review | [7d6734](https://act-rules.github.io/rules/7d6734) | +| [td-headers-attr](https://dequeuniversity.com/rules/axe/4.9/td-headers-attr?application=RuleDescription) | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [a25f45](https://act-rules.github.io/rules/a25f45) | +| [th-has-data-cells](https://dequeuniversity.com/rules/axe/4.9/th-has-data-cells?application=RuleDescription) | Ensure that <th> elements and elements with role=columnheader/rowheader have data cells they describe | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure, needs review | [d0f69e](https://act-rules.github.io/rules/d0f69e) | +| [valid-lang](https://dequeuniversity.com/rules/axe/4.9/valid-lang?application=RuleDescription) | Ensures lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | +| [video-caption](https://dequeuniversity.com/rules/axe/4.9/video-caption?application=RuleDescription) | Ensures <video> elements have captions | Critical | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a, TTv5, TT17.a, EN-301-549, EN-9.1.2.2 | needs review | [eac66b](https://act-rules.github.io/rules/eac66b) | ## WCAG 2.1 Level A & AA Rules | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.8/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT | failure | [73f2c2](https://act-rules.github.io/rules/73f2c2) | -| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.8/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) | +| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.9/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT | failure | [73f2c2](https://act-rules.github.io/rules/73f2c2) | +| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.9/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) | ## WCAG 2.2 Level A & AA Rules @@ -86,7 +88,7 @@ These rules are disabled by default, until WCAG 2.2 is more widely adopted and r | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :----------------------------------------------------------------------------------------------- | :------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :-------- | -| [target-size](https://dequeuniversity.com/rules/axe/4.8/target-size?application=RuleDescription) | Ensure touch target have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | +| [target-size](https://dequeuniversity.com/rules/axe/4.9/target-size?application=RuleDescription) | Ensure touch target have sufficient size and space | Serious | cat.sensory-and-visual-cues, wcag22aa, wcag258 | failure, needs review | | ## Best Practices Rules @@ -94,34 +96,34 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :----------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------- | :------------------------- | :------------------------------------------------- | -| [accesskeys](https://dequeuniversity.com/rules/axe/4.8/accesskeys?application=RuleDescription) | Ensures every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | -| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-role?application=RuleDescription) | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | -| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.8/aria-dialog-name?application=RuleDescription) | Ensures every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | -| [aria-text](https://dequeuniversity.com/rules/axe/4.8/aria-text?application=RuleDescription) | Ensures role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | -| [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.8/aria-treeitem-name?application=RuleDescription) | Ensures every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | -| [empty-heading](https://dequeuniversity.com/rules/axe/4.8/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | [ffd0e9](https://act-rules.github.io/rules/ffd0e9) | -| [empty-table-header](https://dequeuniversity.com/rules/axe/4.8/empty-table-header?application=RuleDescription) | Ensures table headers have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | | -| [frame-tested](https://dequeuniversity.com/rules/axe/4.8/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, best-practice, review-item | failure, needs review | | -| [heading-order](https://dequeuniversity.com/rules/axe/4.8/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure, needs review | | -| [image-redundant-alt](https://dequeuniversity.com/rules/axe/4.8/image-redundant-alt?application=RuleDescription) | Ensure image alternative is not repeated as text | Minor | cat.text-alternatives, best-practice | failure | | -| [label-title-only](https://dequeuniversity.com/rules/axe/4.8/label-title-only?application=RuleDescription) | Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Serious | cat.forms, best-practice | failure | | -| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.8/landmark-banner-is-top-level?application=RuleDescription) | Ensures the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.8/landmark-complementary-is-top-level?application=RuleDescription) | Ensures the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.8/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensures the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.8/landmark-main-is-top-level?application=RuleDescription) | Ensures the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.8/landmark-no-duplicate-banner?application=RuleDescription) | Ensures the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.8/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.8/landmark-no-duplicate-main?application=RuleDescription) | Ensures the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.8/landmark-one-main?application=RuleDescription) | Ensures the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | -| [landmark-unique](https://dequeuniversity.com/rules/axe/4.8/landmark-unique?application=RuleDescription) | Landmarks should have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | failure | | -| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.8/meta-viewport-large?application=RuleDescription) | Ensures <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | -| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.8/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | -| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.8/presentation-role-conflict?application=RuleDescription) | Elements marked as presentational should not have global ARIA or tabindex to ensure all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | -| [region](https://dequeuniversity.com/rules/axe/4.8/region?application=RuleDescription) | Ensures all page content is contained by landmarks | Moderate | cat.keyboard, best-practice | failure | | -| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.8/scope-attr-valid?application=RuleDescription) | Ensures the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | -| [skip-link](https://dequeuniversity.com/rules/axe/4.8/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice | failure, needs review | | -| [tabindex](https://dequeuniversity.com/rules/axe/4.8/tabindex?application=RuleDescription) | Ensures tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | -| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.8/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice | failure, needs review | | +| [accesskeys](https://dequeuniversity.com/rules/axe/4.9/accesskeys?application=RuleDescription) | Ensures every accesskey attribute value is unique | Serious | cat.keyboard, best-practice | failure | | +| [aria-allowed-role](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role?application=RuleDescription) | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | | +| [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.9/aria-dialog-name?application=RuleDescription) | Ensures every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | +| [aria-text](https://dequeuniversity.com/rules/axe/4.9/aria-text?application=RuleDescription) | Ensures role="text" is used on elements with no focusable descendants | Serious | cat.aria, best-practice | failure, needs review | | +| [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.9/aria-treeitem-name?application=RuleDescription) | Ensures every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | +| [empty-heading](https://dequeuniversity.com/rules/axe/4.9/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | [ffd0e9](https://act-rules.github.io/rules/ffd0e9) | +| [empty-table-header](https://dequeuniversity.com/rules/axe/4.9/empty-table-header?application=RuleDescription) | Ensures table headers have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | | +| [frame-tested](https://dequeuniversity.com/rules/axe/4.9/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, best-practice, review-item | failure, needs review | | +| [heading-order](https://dequeuniversity.com/rules/axe/4.9/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure, needs review | | +| [image-redundant-alt](https://dequeuniversity.com/rules/axe/4.9/image-redundant-alt?application=RuleDescription) | Ensure image alternative is not repeated as text | Minor | cat.text-alternatives, best-practice | failure | | +| [label-title-only](https://dequeuniversity.com/rules/axe/4.9/label-title-only?application=RuleDescription) | Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Serious | cat.forms, best-practice | failure | | +| [landmark-banner-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-banner-is-top-level?application=RuleDescription) | Ensures the banner landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-complementary-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-complementary-is-top-level?application=RuleDescription) | Ensures the complementary landmark or aside is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-contentinfo-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-contentinfo-is-top-level?application=RuleDescription) | Ensures the contentinfo landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-main-is-top-level](https://dequeuniversity.com/rules/axe/4.9/landmark-main-is-top-level?application=RuleDescription) | Ensures the main landmark is at top level | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-banner?application=RuleDescription) | Ensures the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.9/landmark-no-duplicate-main?application=RuleDescription) | Ensures the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.9/landmark-one-main?application=RuleDescription) | Ensures the document has a main landmark | Moderate | cat.semantics, best-practice | failure | | +| [landmark-unique](https://dequeuniversity.com/rules/axe/4.9/landmark-unique?application=RuleDescription) | Landmarks should have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | failure | | +| [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.9/meta-viewport-large?application=RuleDescription) | Ensures <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | +| [page-has-heading-one](https://dequeuniversity.com/rules/axe/4.9/page-has-heading-one?application=RuleDescription) | Ensure that the page, or at least one of its frames contains a level-one heading | Moderate | cat.semantics, best-practice | failure | | +| [presentation-role-conflict](https://dequeuniversity.com/rules/axe/4.9/presentation-role-conflict?application=RuleDescription) | Elements marked as presentational should not have global ARIA or tabindex to ensure all screen readers ignore them | Minor | cat.aria, best-practice, ACT | failure | [46ca7f](https://act-rules.github.io/rules/46ca7f) | +| [region](https://dequeuniversity.com/rules/axe/4.9/region?application=RuleDescription) | Ensures all page content is contained by landmarks | Moderate | cat.keyboard, best-practice | failure | | +| [scope-attr-valid](https://dequeuniversity.com/rules/axe/4.9/scope-attr-valid?application=RuleDescription) | Ensures the scope attribute is used correctly on tables | Moderate | cat.tables, best-practice | failure | | +| [skip-link](https://dequeuniversity.com/rules/axe/4.9/skip-link?application=RuleDescription) | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice | failure, needs review | | +| [tabindex](https://dequeuniversity.com/rules/axe/4.9/tabindex?application=RuleDescription) | Ensures tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | failure | | +| [table-duplicate-name](https://dequeuniversity.com/rules/axe/4.9/table-duplicate-name?application=RuleDescription) | Ensure the <caption> element does not contain the same text as the summary attribute | Minor | cat.tables, best-practice | failure, needs review | | ## WCAG 2.x level AAA rules @@ -129,9 +131,9 @@ Rules that check for conformance to WCAG AAA success criteria that can be fully | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | :------ | :--------------------------------------------- | :------------------------- | :------------------------------------------------- | -| [color-contrast-enhanced](https://dequeuniversity.com/rules/axe/4.8/color-contrast-enhanced?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds | Serious | cat.color, wcag2aaa, wcag146, ACT | failure, needs review | [09o5cg](https://act-rules.github.io/rules/09o5cg) | -| [identical-links-same-purpose](https://dequeuniversity.com/rules/axe/4.8/identical-links-same-purpose?application=RuleDescription) | Ensure that links with the same accessible name serve a similar purpose | Minor | cat.semantics, wcag2aaa, wcag249 | needs review | [b20e66](https://act-rules.github.io/rules/b20e66) | -| [meta-refresh-no-exceptions](https://dequeuniversity.com/rules/axe/4.8/meta-refresh-no-exceptions?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Minor | cat.time-and-media, wcag2aaa, wcag224, wcag325 | failure | [bisz58](https://act-rules.github.io/rules/bisz58) | +| [color-contrast-enhanced](https://dequeuniversity.com/rules/axe/4.9/color-contrast-enhanced?application=RuleDescription) | Ensures the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds | Serious | cat.color, wcag2aaa, wcag146, ACT | failure, needs review | [09o5cg](https://act-rules.github.io/rules/09o5cg) | +| [identical-links-same-purpose](https://dequeuniversity.com/rules/axe/4.9/identical-links-same-purpose?application=RuleDescription) | Ensure that links with the same accessible name serve a similar purpose | Minor | cat.semantics, wcag2aaa, wcag249 | needs review | [b20e66](https://act-rules.github.io/rules/b20e66) | +| [meta-refresh-no-exceptions](https://dequeuniversity.com/rules/axe/4.9/meta-refresh-no-exceptions?application=RuleDescription) | Ensures <meta http-equiv="refresh"> is not used for delayed refresh | Minor | cat.time-and-media, wcag2aaa, wcag224, wcag325 | failure | [bisz58](https://act-rules.github.io/rules/bisz58) | ## Experimental Rules @@ -139,13 +141,13 @@ Rules we are still testing and developing. They are disabled by default in axe-c | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------- | :------------------------- | :------------------------------------------------- | -| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.8/css-orientation-lock?application=RuleDescription) | Ensures content is not locked to any specific display orientation, and the content is operable in all display orientations | Serious | cat.structure, wcag134, wcag21aa, EN-301-549, EN-9.1.3.4, experimental | failure, needs review | [b33eff](https://act-rules.github.io/rules/b33eff) | -| [focus-order-semantics](https://dequeuniversity.com/rules/axe/4.8/focus-order-semantics?application=RuleDescription) | Ensures elements in the focus order have a role appropriate for interactive content | Minor | cat.keyboard, best-practice, experimental | failure | | -| [hidden-content](https://dequeuniversity.com/rules/axe/4.8/hidden-content?application=RuleDescription) | Informs users about hidden content. | Minor | cat.structure, best-practice, experimental, review-item | failure, needs review | | -| [label-content-name-mismatch](https://dequeuniversity.com/rules/axe/4.8/label-content-name-mismatch?application=RuleDescription) | Ensures that elements labelled through their content must have their visible text as part of their accessible name | Serious | cat.semantics, wcag21a, wcag253, EN-301-549, EN-9.2.5.3, experimental | failure | [2ee8b8](https://act-rules.github.io/rules/2ee8b8) | -| [p-as-heading](https://dequeuniversity.com/rules/axe/4.8/p-as-heading?application=RuleDescription) | Ensure bold, italic text and font-size is not used to style <p> elements as a heading | Serious | cat.semantics, wcag2a, wcag131, EN-301-549, EN-9.1.3.1, experimental | failure, needs review | | -| [table-fake-caption](https://dequeuniversity.com/rules/axe/4.8/table-fake-caption?application=RuleDescription) | Ensure that tables with a caption use the <caption> element. | Serious | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, EN-301-549, EN-9.1.3.1 | failure | | -| [td-has-header](https://dequeuniversity.com/rules/axe/4.8/td-has-header?application=RuleDescription) | Ensure that each non-empty data cell in a <table> larger than 3 by 3 has one or more table headers | Critical | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure | | +| [css-orientation-lock](https://dequeuniversity.com/rules/axe/4.9/css-orientation-lock?application=RuleDescription) | Ensures content is not locked to any specific display orientation, and the content is operable in all display orientations | Serious | cat.structure, wcag134, wcag21aa, EN-301-549, EN-9.1.3.4, experimental | failure, needs review | [b33eff](https://act-rules.github.io/rules/b33eff) | +| [focus-order-semantics](https://dequeuniversity.com/rules/axe/4.9/focus-order-semantics?application=RuleDescription) | Ensures elements in the focus order have a role appropriate for interactive content | Minor | cat.keyboard, best-practice, experimental | failure | | +| [hidden-content](https://dequeuniversity.com/rules/axe/4.9/hidden-content?application=RuleDescription) | Informs users about hidden content. | Minor | cat.structure, best-practice, experimental, review-item | failure, needs review | | +| [label-content-name-mismatch](https://dequeuniversity.com/rules/axe/4.9/label-content-name-mismatch?application=RuleDescription) | Ensures that elements labelled through their content must have their visible text as part of their accessible name | Serious | cat.semantics, wcag21a, wcag253, EN-301-549, EN-9.2.5.3, experimental | failure | [2ee8b8](https://act-rules.github.io/rules/2ee8b8) | +| [p-as-heading](https://dequeuniversity.com/rules/axe/4.9/p-as-heading?application=RuleDescription) | Ensure bold, italic text and font-size is not used to style <p> elements as a heading | Serious | cat.semantics, wcag2a, wcag131, EN-301-549, EN-9.1.3.1, experimental | failure, needs review | | +| [table-fake-caption](https://dequeuniversity.com/rules/axe/4.9/table-fake-caption?application=RuleDescription) | Ensure that tables with a caption use the <caption> element. | Serious | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, EN-301-549, EN-9.1.3.1 | failure | | +| [td-has-header](https://dequeuniversity.com/rules/axe/4.9/td-has-header?application=RuleDescription) | Ensure that each non-empty data cell in a <table> larger than 3 by 3 has one or more table headers | Critical | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1 | failure | | ## Deprecated Rules @@ -153,7 +155,7 @@ Deprecated rules are disabled by default and will be removed in the next major r | Rule ID | Description | Impact | Tags | Issue Type | ACT Rules | | :----------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | :----------------------------------------------------------------------------------------------------- | -| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.8/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | -| [audio-caption](https://dequeuniversity.com/rules/axe/4.8/audio-caption?application=RuleDescription) | Ensures <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | -| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.8/duplicate-id-active?application=RuleDescription) | Ensures every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | -| [duplicate-id](https://dequeuniversity.com/rules/axe/4.8/duplicate-id?application=RuleDescription) | Ensures every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [aria-roledescription](https://dequeuniversity.com/rules/axe/4.9/aria-roledescription?application=RuleDescription) | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, deprecated | failure, needs review | | +| [audio-caption](https://dequeuniversity.com/rules/axe/4.9/audio-caption?application=RuleDescription) | Ensures <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, EN-301-549, EN-9.1.2.1, section508, section508.22.a, deprecated | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | +| [duplicate-id-active](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-active?application=RuleDescription) | Ensures every id attribute value of active elements is unique | Serious | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | +| [duplicate-id](https://dequeuniversity.com/rules/axe/4.9/duplicate-id?application=RuleDescription) | Ensures every id attribute value is unique | Minor | cat.parsing, wcag2a-obsolete, wcag411, deprecated | failure | [3ea0c8](https://act-rules.github.io/rules/3ea0c8) | diff --git a/lib/checks/aria/aria-busy.json b/lib/checks/aria/aria-busy.json index e97624d046..da2162130f 100644 --- a/lib/checks/aria/aria-busy.json +++ b/lib/checks/aria/aria-busy.json @@ -1,6 +1,7 @@ { "id": "aria-busy", "evaluate": "aria-busy-evaluate", + "deprecated": true, "metadata": { "impact": "serious", "messages": { diff --git a/lib/checks/aria/aria-required-children-evaluate.js b/lib/checks/aria/aria-required-children-evaluate.js index 226bd54703..1e7512d960 100644 --- a/lib/checks/aria/aria-required-children-evaluate.js +++ b/lib/checks/aria/aria-required-children-evaluate.js @@ -55,6 +55,11 @@ export default function ariaRequiredChildrenEvaluate( return true; } + if (virtualNode.attr('aria-busy') === 'true') { + this.data({ messageKey: 'aria-busy' }); + return true; + } + this.data(required); // Only review empty nodes when a node is both empty and does not have an aria-owns relationship diff --git a/lib/checks/aria/aria-required-children.json b/lib/checks/aria/aria-required-children.json index db1e2bf630..16c67dbd51 100644 --- a/lib/checks/aria/aria-required-children.json +++ b/lib/checks/aria/aria-required-children.json @@ -20,7 +20,10 @@ "metadata": { "impact": "critical", "messages": { - "pass": "Required ARIA children are present", + "pass": { + "default": "Required ARIA children are present", + "aria-busy": "Element has an aria-busy attribute, so it is allowed to omit required children" + }, "fail": { "singular": "Required ARIA child role not present: ${data.values}", "plural": "Required ARIA children role not present: ${data.values}", diff --git a/lib/checks/color/color-contrast-evaluate.js b/lib/checks/color/color-contrast-evaluate.js index 3268156e62..8916bd0971 100644 --- a/lib/checks/color/color-contrast-evaluate.js +++ b/lib/checks/color/color-contrast-evaluate.js @@ -187,38 +187,37 @@ function findPseudoElement( } while ((vNode = vNode.parent)); } -const getPseudoElementArea = memoize(function getPseudoElementArea( - node, - pseudo -) { - const style = window.getComputedStyle(node, pseudo); - const matchPseudoStyle = (prop, value) => - style.getPropertyValue(prop) === value; - if ( - matchPseudoStyle('content', 'none') || - matchPseudoStyle('display', 'none') || - matchPseudoStyle('visibility', 'hidden') || - matchPseudoStyle('position', 'absolute') === false - ) { - return 0; // The pseudo element isn't visible - } +const getPseudoElementArea = memoize( + function getPseudoElementArea(node, pseudo) { + const style = window.getComputedStyle(node, pseudo); + const matchPseudoStyle = (prop, value) => + style.getPropertyValue(prop) === value; + if ( + matchPseudoStyle('content', 'none') || + matchPseudoStyle('display', 'none') || + matchPseudoStyle('visibility', 'hidden') || + matchPseudoStyle('position', 'absolute') === false + ) { + return 0; // The pseudo element isn't visible + } - if ( - getOwnBackgroundColor(style).alpha === 0 && - matchPseudoStyle('background-image', 'none') - ) { - return 0; // There is no background - } + if ( + getOwnBackgroundColor(style).alpha === 0 && + matchPseudoStyle('background-image', 'none') + ) { + return 0; // There is no background + } - // Find the size of the pseudo element; - const pseudoWidth = parseUnit(style.getPropertyValue('width')); - const pseudoHeight = parseUnit(style.getPropertyValue('height')); - if (pseudoWidth.unit !== 'px' || pseudoHeight.unit !== 'px') { - // IE doesn't normalize to px. Infinity gets everything to undefined - return pseudoWidth.value === 0 || pseudoHeight.value === 0 ? 0 : Infinity; + // Find the size of the pseudo element; + const pseudoWidth = parseUnit(style.getPropertyValue('width')); + const pseudoHeight = parseUnit(style.getPropertyValue('height')); + if (pseudoWidth.unit !== 'px' || pseudoHeight.unit !== 'px') { + // IE doesn't normalize to px. Infinity gets everything to undefined + return pseudoWidth.value === 0 || pseudoHeight.value === 0 ? 0 : Infinity; + } + return pseudoWidth.value * pseudoHeight.value; } - return pseudoWidth.value * pseudoHeight.value; -}); +); function textIsEmojis(visibleText) { const options = { nonBmp: true }; diff --git a/lib/checks/lists/listitem.json b/lib/checks/lists/listitem.json index 8efdba6f10..c8f2785249 100644 --- a/lib/checks/lists/listitem.json +++ b/lib/checks/lists/listitem.json @@ -7,7 +7,7 @@ "pass": "List item has a
    ,
      or role=\"list\" parent element", "fail": { "default": "List item does not have a
        ,
          parent element", - "roleNotValid": "List item does not have a
            ,
              parent element without a role, or a role=\"list\"" + "roleNotValid": "List item parent element has a role that is not role=\"list\"" } } } diff --git a/lib/checks/mobile/target-offset-evaluate.js b/lib/checks/mobile/target-offset-evaluate.js index e799cb68f1..8e45fc8164 100644 --- a/lib/checks/mobile/target-offset-evaluate.js +++ b/lib/checks/mobile/target-offset-evaluate.js @@ -1,11 +1,18 @@ import { findNearbyElms, isFocusable, isInTabOrder } from '../../commons/dom'; import { getRoleType } from '../../commons/aria'; -import { getOffset } from '../../commons/math'; +import { getOffset, rectHasMinimumSize } from '../../commons/math'; const roundingMargin = 0.05; export default function targetOffsetEvaluate(node, options, vNode) { const minOffset = options?.minOffset || 24; + // Bail early to avoid hitting very expensive calculations. + // Targets are so large they are unlikely to fail. + if (rectHasMinimumSize(minOffset * 10, vNode.boundingClientRect)) { + this.data({ messageKey: 'large', minOffset }); + return true; + } + const closeNeighbors = []; let closestOffset = minOffset; for (const vNeighbor of findNearbyElms(vNode, minOffset)) { diff --git a/lib/checks/mobile/target-offset.json b/lib/checks/mobile/target-offset.json index 2aebbdf9f4..c06ee11d7a 100644 --- a/lib/checks/mobile/target-offset.json +++ b/lib/checks/mobile/target-offset.json @@ -7,7 +7,10 @@ "metadata": { "impact": "serious", "messages": { - "pass": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "pass": { + "default": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "large": "Target far exceeds the minimum size of ${data.minOffset}px." + }, "fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.", "incomplete": { "default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?", diff --git a/lib/checks/mobile/target-size-evaluate.js b/lib/checks/mobile/target-size-evaluate.js index a0224c8ad2..6ee45bc9c5 100644 --- a/lib/checks/mobile/target-size-evaluate.js +++ b/lib/checks/mobile/target-size-evaluate.js @@ -10,9 +10,16 @@ import { * Determine if an element has a minimum size, taking into account * any elements that may obscure it. */ -export default function targetSize(node, options, vNode) { +export default function targetSizeEvaluate(node, options, vNode) { const minSize = options?.minSize || 24; const nodeRect = vNode.boundingClientRect; + // Bail early to avoid hitting very expensive calculations. + // Targets are so large they are unlikely to fail. + if (rectHasMinimumSize(minSize * 10, nodeRect)) { + this.data({ messageKey: 'large', minSize }); + return true; + } + const hasMinimumSize = rectHasMinimumSize.bind(null, minSize); const nearbyElms = findNearbyElms(vNode); const overflowingContent = filterOverflowingContent(vNode, nearbyElms); @@ -21,8 +28,20 @@ export default function targetSize(node, options, vNode) { nearbyElms ); + // Target has overflowing content; + // and is either not fully obscured (so may not pass), + // or has insufficient space (and so may not fail) + if ( + overflowingContent.length && + (fullyObscuringElms.length || !hasMinimumSize(nodeRect)) + ) { + this.data({ minSize, messageKey: 'contentOverflow' }); + this.relatedNodes(mapActualNodes(overflowingContent)); + return undefined; + } + // Target is fully obscured and no overflowing content (which may not be obscured) - if (fullyObscuringElms.length && !overflowingContent.length) { + if (fullyObscuringElms.length) { this.relatedNodes(mapActualNodes(fullyObscuringElms)); this.data({ messageKey: 'obscured' }); return true; @@ -32,31 +51,34 @@ export default function targetSize(node, options, vNode) { const negativeOutcome = isInTabOrder(vNode) ? false : undefined; // Target is too small, and has no overflowing content that increases the size - if (!hasMinimumSize(nodeRect) && !overflowingContent.length) { + if (!hasMinimumSize(nodeRect)) { this.data({ minSize, ...toDecimalSize(nodeRect) }); return negativeOutcome; } // Figure out the largest space on the target, not obscured by other widgets const obscuredWidgets = filterFocusableWidgets(partialObscuringElms); + + // Target not obscured and has sufficient space + if (!obscuredWidgets.length) { + this.data({ minSize, ...toDecimalSize(nodeRect) }); + return true; + } + const largestInnerRect = getLargestUnobscuredArea(vNode, obscuredWidgets); + if (!largestInnerRect) { + this.data({ minSize, messageKey: 'tooManyRects' }); + return undefined; + } - // Target has overflowing content; - // and is either not fully obscured (so may not pass), - // or has insufficient space (and so may not fail) - if (overflowingContent.length) { - if ( - fullyObscuringElms.length || - !hasMinimumSize(largestInnerRect || nodeRect) - ) { + // Target is obscured, and insufficient space is left + if (!hasMinimumSize(largestInnerRect)) { + if (overflowingContent.length) { this.data({ minSize, messageKey: 'contentOverflow' }); this.relatedNodes(mapActualNodes(overflowingContent)); return undefined; } - } - // Target is obscured, and insufficient space is left - if (obscuredWidgets.length !== 0 && !hasMinimumSize(largestInnerRect)) { const allTabbable = obscuredWidgets.every(isInTabOrder); const messageKey = `partiallyObscured${allTabbable ? '' : 'NonTabbable'}`; @@ -65,7 +87,7 @@ export default function targetSize(node, options, vNode) { return allTabbable ? negativeOutcome : undefined; } - // Target not obscured, or has sufficient space + // Target has sufficient space this.data({ minSize, ...toDecimalSize(largestInnerRect || nodeRect) }); this.relatedNodes(mapActualNodes(obscuredWidgets)); return true; @@ -106,13 +128,14 @@ function filterByElmsOverlap(vNode, nearbyElms) { // Find areas of the target that are not obscured function getLargestUnobscuredArea(vNode, obscuredNodes) { const nodeRect = vNode.boundingClientRect; - if (obscuredNodes.length === 0) { - return null; - } const obscuringRects = obscuredNodes.map( ({ boundingClientRect: rect }) => rect ); const unobscuredRects = splitRects(nodeRect, obscuringRects); + if (unobscuredRects.length === 0) { + return null; + } + // Of the unobscured inner rects, work out the largest return getLargestRect(unobscuredRects); } diff --git a/lib/checks/mobile/target-size.json b/lib/checks/mobile/target-size.json index 0fff548a65..302e4f985e 100644 --- a/lib/checks/mobile/target-size.json +++ b/lib/checks/mobile/target-size.json @@ -9,7 +9,8 @@ "messages": { "pass": { "default": "Control has sufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", - "obscured": "Control is ignored because it is fully obscured and thus not clickable" + "obscured": "Control is ignored because it is fully obscured and thus not clickable", + "large": "Target far exceeds the minimum size of ${data.minSize}px." }, "fail": { "default": "Target has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", @@ -19,7 +20,8 @@ "default": "Element with negative tabindex has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is this a target?", "contentOverflow": "Element size could not be accurately determined due to overflow content", "partiallyObscured": "Element with negative tabindex has insufficient size because it is partially obscured (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is this a target?", - "partiallyObscuredNonTabbable": "Target has insufficient size because it is partially obscured by a neighbor with negative tabindex (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is the neighbor a target?" + "partiallyObscuredNonTabbable": "Target has insufficient size because it is partially obscured by a neighbor with negative tabindex (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is the neighbor a target?", + "tooManyRects": "Could not get the target size because there are too many overlapping elements" } } } diff --git a/lib/commons/color/color.js b/lib/commons/color/color.js index 15a33f1d60..5201628d38 100644 --- a/lib/commons/color/color.js +++ b/lib/commons/color/color.js @@ -1,7 +1,6 @@ import { Colorjs } from '../../core/imports'; const hexRegex = /^#[0-9a-f]{3,8}$/i; -const hslRegex = /hsl\(\s*([\d.]+)(rad|turn)/; /** * @class Color @@ -12,7 +11,26 @@ const hslRegex = /hsl\(\s*([\d.]+)(rad|turn)/; * @param {number} alpha */ export default class Color { + // color channel values typically in the range of 0-1 (can go below or above) + #r; + #g; + #b; + // color component values resolved to the sRGB color space (0-255) + #red; + #green; + #blue; + constructor(red, green, blue, alpha = 1) { + if (red instanceof Color) { + // preserve out of gamut values + const { r, g, b } = red; + this.r = r; + this.g = g; + this.b = b; + this.alpha = red.alpha; + return; + } + /** @type {number} */ this.red = red; @@ -26,6 +44,60 @@ export default class Color { this.alpha = alpha; } + get r() { + return this.#r; + } + + set r(value) { + this.#r = value; + this.#red = Math.round(clamp(value, 0, 1) * 255); + } + + get g() { + return this.#g; + } + + set g(value) { + this.#g = value; + this.#green = Math.round(clamp(value, 0, 1) * 255); + } + + get b() { + return this.#b; + } + + set b(value) { + this.#b = value; + this.#blue = Math.round(clamp(value, 0, 1) * 255); + } + + get red() { + return this.#red; + } + + set red(value) { + this.#r = value / 255; + this.#red = clamp(value, 0, 255); + } + + get green() { + return this.#green; + } + + set green(value) { + this.#g = value / 255; + this.#green = clamp(value, 0, 255); + } + + get blue() { + return this.#blue; + } + + set blue(value) { + this.#b = value / 255; + this.#blue = clamp(value, 0, 255); + } + /** * Provide the hex string value for the color * @method toHexString @@ -34,9 +106,9 @@ export default class Color { * @return {string} */ toHexString() { - var redString = Math.round(this.red).toString(16); - var greenString = Math.round(this.green).toString(16); - var blueString = Math.round(this.blue).toString(16); + const redString = Math.round(this.red).toString(16); + const greenString = Math.round(this.green).toString(16); + const blueString = Math.round(this.blue).toString(16); return ( '#' + (this.red > 15.5 ? redString : '0' + redString) + @@ -57,28 +129,12 @@ export default class Color { * @instance */ parseString(colorString) { - // Colorjs currently does not support rad or turn angle values - // @see https://github.com/LeaVerou/color.js/issues/311 - colorString = colorString.replace(hslRegex, (match, angle, unit) => { - const value = angle + unit; - - switch (unit) { - case 'rad': - return match.replace(value, radToDeg(angle)); - case 'turn': - return match.replace(value, turnToDeg(angle)); - } - }); - try { // srgb values are between 0 and 1 const color = new Colorjs(colorString).to('srgb'); - // when converting from one color space to srgb - // the values of rgb may be above 1 so we need to clamp them - // we also need to round the final value as rgb values don't have decimals - this.red = Math.round(clamp(color.r, 0, 1) * 255); - this.green = Math.round(clamp(color.g, 0, 1) * 255); - this.blue = Math.round(clamp(color.b, 0, 1) * 255); + this.r = color.r; + this.g = color.g; + this.b = color.b; // color.alpha is a Number object so convert it to a number this.alpha = +color.alpha; } catch (err) { @@ -137,32 +193,138 @@ export default class Color { * @return {number} The luminance value, ranges from 0 to 1 */ getRelativeLuminance() { - var rSRGB = this.red / 255; - var gSRGB = this.green / 255; - var bSRGB = this.blue / 255; + const { r: rSRGB, g: gSRGB, b: bSRGB } = this; - var r = + const r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4); - var g = + const g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4); - var b = + const b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } + + /** + * Add a value to the color channels + * @private + * @param {number} value The value to add + * @return {Color} A new color instance + */ + #add(value) { + const C = new Color(this); + C.r += value; + C.g += value; + C.b += value; + return C; + } + + /** + * Get the luminosity of a color + * using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @method getLuminosity + * @memberof axe.commons.color.Color + * @instance + * @return {number} The luminosity of the color + */ + getLuminosity() { + return 0.3 * this.r + 0.59 * this.g + 0.11 * this.b; + } + + /** + * Set the luminosity of a color + * using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @method setLuminosity + * @memberof axe.commons.color.Color + * @instance + * @param {number} L The luminosity + * @return {Color} A new color instance + */ + setLuminosity(L) { + const d = L - this.getLuminosity(); + return this.#add(d).clip(); + } + + /** + * Get the saturation of a color + * using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @method getSaturation + * @memberof axe.commons.color.Color + * @instance + * @return {number} The saturation of the color + */ + getSaturation() { + return Math.max(this.r, this.g, this.b) - Math.min(this.r, this.g, this.b); + } + + /** + * Set the saturation of a color + * using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @method setSaturation + * @memberof axe.commons.color.Color + * @instance + * @param {number} s The saturation + * @return {Color} A new color instance + */ + setSaturation(s) { + const C = new Color(this); + const colorEntires = [ + { name: 'r', value: C.r }, + { name: 'g', value: C.g }, + { name: 'b', value: C.b } + ]; + + // find the min, mid, and max values of the color components + const [Cmin, Cmid, Cmax] = colorEntires.sort((a, b) => { + return a.value - b.value; + }); + + if (Cmax.value > Cmin.value) { + Cmid.value = ((Cmid.value - Cmin.value) * s) / (Cmax.value - Cmin.value); + Cmax.value = s; + } else { + Cmid.value = Cmax.value = 0; + } + + Cmin.value = 0; + + C[Cmax.name] = Cmax.value; + C[Cmin.name] = Cmin.value; + C[Cmid.name] = Cmid.value; + return C; + } + + /** + * Clip the color between RGB 0-1 accounting for the luminosity of the color. Color must be normalized before calling. + * using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable + * @method clip + * @memberof axe.commons.color.Color + * @instance + * @return {Color} A new color instance clipped between 0-1 + */ + clip() { + const C = new Color(this); + const L = C.getLuminosity(); + const n = Math.min(C.r, C.g, C.b); + const x = Math.max(C.r, C.g, C.b); + + if (n < 0) { + C.r = L + ((C.r - L) * L) / (L - n); + C.g = L + ((C.g - L) * L) / (L - n); + C.b = L + ((C.b - L) * L) / (L - n); + } + + if (x > 1) { + C.r = L + ((C.r - L) * (1 - L)) / (x - L); + C.g = L + ((C.g - L) * (1 - L)) / (x - L); + C.b = L + ((C.b - L) * (1 - L)) / (x - L); + } + + return C; + } } // clamp a value between two numbers (inclusive) function clamp(value, min, max) { return Math.min(Math.max(min, value), max); } - -// convert radians to degrees -function radToDeg(rad) { - return (rad * 180) / Math.PI; -} - -// convert turn to degrees -function turnToDeg(turn) { - return turn * 360; -} diff --git a/lib/commons/color/flatten-colors.js b/lib/commons/color/flatten-colors.js index 9ac19f0d11..e70ceafd33 100644 --- a/lib/commons/color/flatten-colors.js +++ b/lib/commons/color/flatten-colors.js @@ -1,9 +1,7 @@ import Color from './color'; -// clamp a value between two numbers (inclusive) -function clamp(value, min, max) { - return Math.min(Math.max(min, value), max); -} +// @see https://www.w3.org/TR/compositing-1/#blendingnonseparable +const nonSeparableBlendModes = ['hue', 'saturation', 'color', 'luminosity']; // how to combine background and foreground colors together when using // the CSS property `mix-blend-mode`. Defaults to `normal` @@ -61,28 +59,32 @@ const blendFunctions = { exclusion(Cb, Cs) { // @see https://www.w3.org/TR/compositing-1/#blendingexclusion return Cb + Cs - 2 * Cb * Cs; + }, + + // non-separate color function take the entire color object + // and not individual color components (red, green, blue) + hue(Cb, Cs) { + // @see https://www.w3.org/TR/compositing-1/#blendinghue + return Cs.setSaturation(Cb.getSaturation()).setLuminosity( + Cb.getLuminosity() + ); + }, + saturation(Cb, Cs) { + // @see https://www.w3.org/TR/compositing-1/#blendingsaturation + return Cb.setSaturation(Cs.getSaturation()).setLuminosity( + Cb.getLuminosity() + ); + }, + color(Cb, Cs) { + // @see https://www.w3.org/TR/compositing-1/#blendingcolor + return Cs.setLuminosity(Cb.getLuminosity()); + }, + luminosity(Cb, Cs) { + // @see https://www.w3.org/TR/compositing-1/#blendingluminosity + return Cb.setLuminosity(Cs.getLuminosity()); } }; -// Simple Alpha Compositing written as non-premultiplied. -// formula: Rrgb × Ra = Srgb × Sa + Drgb × Da × (1 − Sa) -// Cs: the source color -// αs: the source alpha -// Cb: the backdrop color -// αb: the backdrop alpha -// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing -// @see https://www.w3.org/TR/compositing-1/#blending -// @see https://ciechanow.ski/alpha-compositing/ -function simpleAlphaCompositing(Cs, αs, Cb, αb, blendMode) { - return ( - αs * (1 - αb) * Cs + - // Note: Cs and Cb values need to be between 0 and 1 inclusive for the blend function - // @see https://www.w3.org/TR/compositing-1/#simplealphacompositing - αs * αb * blendFunctions[blendMode](Cb / 255, Cs / 255) * 255 + - (1 - αs) * αb * Cb - ); -} - /** * Combine the two given color according to alpha blending. * @method flattenColors @@ -92,28 +94,35 @@ function simpleAlphaCompositing(Cs, αs, Cb, αb, blendMode) { * @param {Color} backdrop Background color * @return {Color} Blended color */ -function flattenColors(sourceColor, backdrop, blendMode = 'normal') { +export default function flattenColors( + sourceColor, + backdrop, + blendMode = 'normal' +) { + const blendingResult = blend(backdrop, sourceColor, blendMode); + // foreground is the "source" color and background is the "backdrop" color const r = simpleAlphaCompositing( sourceColor.red, sourceColor.alpha, backdrop.red, backdrop.alpha, - blendMode + // we don't want to round the blended value + blendingResult.r * 255 ); const g = simpleAlphaCompositing( sourceColor.green, sourceColor.alpha, backdrop.green, backdrop.alpha, - blendMode + blendingResult.g * 255 ); const b = simpleAlphaCompositing( sourceColor.blue, sourceColor.alpha, backdrop.blue, backdrop.alpha, - blendMode + blendingResult.b * 255 ); // formula: αo = αs + αb x (1 - αs) @@ -143,4 +152,32 @@ function flattenColors(sourceColor, backdrop, blendMode = 'normal') { return new Color(Cr, Cg, Cb, αo); } -export default flattenColors; +// Simple Alpha Compositing written as non-premultiplied. +// formula: Rrgb × Ra = Srgb × Sa + Drgb × Da × (1 − Sa) +// Cs: the source color +// αs: the source alpha +// Cb: the backdrop color +// αb: the backdrop alpha +// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing +// @see https://www.w3.org/TR/compositing-1/#blending +// @see https://ciechanow.ski/alpha-compositing/ +function simpleAlphaCompositing(Cs, αs, Cb, αb, blendingResult) { + return αs * (1 - αb) * Cs + αs * αb * blendingResult + (1 - αs) * αb * Cb; +} + +// clamp a value between two numbers (inclusive) +function clamp(value, min, max) { + return Math.min(Math.max(min, value), max); +} + +function blend(Cb, Cs, blendMode) { + if (nonSeparableBlendModes.includes(blendMode)) { + return blendFunctions[blendMode](Cb, Cs); + } + + const C = new Color(); + ['r', 'g', 'b'].forEach(channel => { + C[channel] = blendFunctions[blendMode](Cb[channel], Cs[channel]); + }); + return C; +} diff --git a/lib/commons/dom/get-visible-child-text-rects.js b/lib/commons/dom/get-visible-child-text-rects.js index 8ac6166012..02611798e8 100644 --- a/lib/commons/dom/get-visible-child-text-rects.js +++ b/lib/commons/dom/get-visible-child-text-rects.js @@ -39,8 +39,14 @@ const getVisibleChildTextRects = memoize( * @see https://github.com/dequelabs/axe-core/issues/2178 * @see https://github.com/dequelabs/axe-core/issues/2483 * @see https://github.com/dequelabs/axe-core/issues/2681 + * + * also need to resize the nodeRect to fit within the bounds of any overflow: hidden ancestors. + * + * @see https://github.com/dequelabs/axe-core/issues/4253 */ - return clientRects.length ? clientRects : [nodeRect]; + return clientRects.length + ? clientRects + : filterHiddenRects([nodeRect], overflowHiddenNodes); } ); export default getVisibleChildTextRects; diff --git a/lib/commons/dom/is-hidden-for-everyone.js b/lib/commons/dom/is-hidden-for-everyone.js index 8f69c799a6..c2924363e7 100644 --- a/lib/commons/dom/is-hidden-for-everyone.js +++ b/lib/commons/dom/is-hidden-for-everyone.js @@ -65,17 +65,16 @@ const isHiddenSelf = memoize(function isHiddenSelfMemoized(vNode, isAncestor) { /** * Check the element and ancestors for visibility state */ -const isHiddenAncestors = memoize(function isHiddenAncestorsMemoized( - vNode, - isAncestor -) { - if (isHiddenSelf(vNode, isAncestor)) { - return true; - } +const isHiddenAncestors = memoize( + function isHiddenAncestorsMemoized(vNode, isAncestor) { + if (isHiddenSelf(vNode, isAncestor)) { + return true; + } - if (!vNode.parent) { - return false; - } + if (!vNode.parent) { + return false; + } - return isHiddenAncestors(vNode.parent, true); -}); + return isHiddenAncestors(vNode.parent, true); + } +); diff --git a/lib/commons/dom/is-inert.js b/lib/commons/dom/is-inert.js index e0a0270908..df09b5c78d 100644 --- a/lib/commons/dom/is-inert.js +++ b/lib/commons/dom/is-inert.js @@ -40,17 +40,16 @@ const isInertSelf = memoize(function isInertSelfMemoized(vNode, isAncestor) { /** * Check the element and ancestors for inert */ -const isInertAncestors = memoize(function isInertAncestorsMemoized( - vNode, - isAncestor -) { - if (isInertSelf(vNode, isAncestor)) { - return true; - } +const isInertAncestors = memoize( + function isInertAncestorsMemoized(vNode, isAncestor) { + if (isInertSelf(vNode, isAncestor)) { + return true; + } - if (!vNode.parent) { - return false; - } + if (!vNode.parent) { + return false; + } - return isInertAncestors(vNode.parent, true); -}); + return isInertAncestors(vNode.parent, true); + } +); diff --git a/lib/commons/dom/is-visible-on-screen.js b/lib/commons/dom/is-visible-on-screen.js index b806870df3..7795335c95 100644 --- a/lib/commons/dom/is-visible-on-screen.js +++ b/lib/commons/dom/is-visible-on-screen.js @@ -30,28 +30,27 @@ export default function isVisibleOnScreen(vNode) { return isVisibleOnScreenVirtual(vNode); } -const isVisibleOnScreenVirtual = memoize(function isVisibleOnScreenMemoized( - vNode, - isAncestor -) { - if (vNode.actualNode && vNode.props.nodeName === 'area') { - return !areaHidden(vNode, isVisibleOnScreenVirtual); - } +const isVisibleOnScreenVirtual = memoize( + function isVisibleOnScreenMemoized(vNode, isAncestor) { + if (vNode.actualNode && vNode.props.nodeName === 'area') { + return !areaHidden(vNode, isVisibleOnScreenVirtual); + } - if (isHiddenForEveryone(vNode, { skipAncestors: true, isAncestor })) { - return false; - } + if (isHiddenForEveryone(vNode, { skipAncestors: true, isAncestor })) { + return false; + } - if ( - vNode.actualNode && - hiddenMethods.some(method => method(vNode, { isAncestor })) - ) { - return false; - } + if ( + vNode.actualNode && + hiddenMethods.some(method => method(vNode, { isAncestor })) + ) { + return false; + } - if (!vNode.parent) { - return true; - } + if (!vNode.parent) { + return true; + } - return isVisibleOnScreenVirtual(vNode.parent, true); -}); + return isVisibleOnScreenVirtual(vNode.parent, true); + } +); diff --git a/lib/commons/math/split-rects.js b/lib/commons/math/split-rects.js index c7d45bafda..ca4ef232a6 100644 --- a/lib/commons/math/split-rects.js +++ b/lib/commons/math/split-rects.js @@ -13,6 +13,13 @@ export default function splitRects(outerRect, overlapRects) { uniqueRects = uniqueRects.reduce((rects, inputRect) => { return rects.concat(splitRect(inputRect, overlapRect)); }, []); + + // exit early if we get too many rects that it starts causing + // a performance bottleneck + // @see https://github.com/dequelabs/axe-core/issues/4359 + if (uniqueRects.length > 4000) { + return []; + } } return uniqueRects; } diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index a90d3c1528..470946705b 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -322,8 +322,8 @@ function getThreeLeastCommonFeatures(elm, selectorData) { return a.species !== b.species && a.species === 'class' ? -1 : a.species === b.species - ? 0 - : 1; + ? 0 + : 1; }); } } diff --git a/lib/rules/aria-hidden-body.json b/lib/rules/aria-hidden-body.json index 20697f7997..a1b74c2c34 100644 --- a/lib/rules/aria-hidden-body.json +++ b/lib/rules/aria-hidden-body.json @@ -4,7 +4,15 @@ "selector": "body", "excludeHidden": false, "matches": "is-initiator-matches", - "tags": ["cat.aria", "wcag2a", "wcag412", "EN-301-549", "EN-9.4.1.2"], + "tags": [ + "cat.aria", + "wcag2a", + "wcag131", + "wcag412", + "EN-301-549", + "EN-9.1.3.1", + "EN-9.4.1.2" + ], "metadata": { "description": "Ensures aria-hidden=\"true\" is not present on the document body.", "help": "aria-hidden=\"true\" must not be present on the document body" diff --git a/lib/rules/aria-required-children.json b/lib/rules/aria-required-children.json index f81242a613..e1b22fb129 100644 --- a/lib/rules/aria-required-children.json +++ b/lib/rules/aria-required-children.json @@ -10,6 +10,6 @@ "help": "Certain ARIA roles must contain particular children" }, "all": [], - "any": ["aria-required-children", "aria-busy"], + "any": ["aria-required-children"], "none": [] } diff --git a/lib/rules/color-contrast-matches.js b/lib/rules/color-contrast-matches.js index d95b1c3890..b76921bc37 100644 --- a/lib/rules/color-contrast-matches.js +++ b/lib/rules/color-contrast-matches.js @@ -4,7 +4,8 @@ import { findUpVirtual, visuallyOverlaps, getRootNode, - isInert + isInert, + getOverflowHiddenAncestors } from '../commons/dom'; import { visibleVirtual, @@ -12,6 +13,7 @@ import { sanitize, isIconLigature } from '../commons/text'; +import { rectsOverlap } from '../commons/math'; import { isDisabled } from '../commons/forms'; import { getNodeFromTree, querySelectorAll, tokenList } from '../core/utils'; @@ -147,14 +149,22 @@ function colorContrastMatches(node, virtualNode) { } } - const rects = range.getClientRects(); - for (let index = 0; index < rects.length; index++) { + const rects = Array.from(range.getClientRects()); + const clippingAncestors = getOverflowHiddenAncestors(virtualNode); + return rects.some(rect => { //check to see if the rectangle impinges - if (visuallyOverlaps(rects[index], node)) { - return true; + const overlaps = visuallyOverlaps(rect, node); + + if (!clippingAncestors.length) { + return overlaps; } - } - return false; + + const withinOverflow = clippingAncestors.some(overflowNode => { + return rectsOverlap(rect, overflowNode.boundingClientRect); + }); + + return overlaps && withinOverflow; + }); } export default colorContrastMatches; diff --git a/lib/rules/scrollable-region-focusable.json b/lib/rules/scrollable-region-focusable.json index eb7073ce73..88bb4ceaaf 100644 --- a/lib/rules/scrollable-region-focusable.json +++ b/lib/rules/scrollable-region-focusable.json @@ -7,10 +7,12 @@ "cat.keyboard", "wcag2a", "wcag211", + "wcag213", "TTv5", "TT4.a", "EN-301-549", - "EN-9.2.1.1" + "EN-9.2.1.1", + "EN-9.2.1.3" ], "actIds": ["0ssw9k"], "metadata": { diff --git a/locales/_template.json b/locales/_template.json index 33a69a793b..a911c60144 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -497,7 +497,10 @@ } }, "aria-required-children": { - "pass": "Required ARIA children are present", + "pass": { + "default": "Required ARIA children are present", + "aria-busy": "Element has an aria-busy attribute, so it is allowed to omit required children" + }, "fail": { "singular": "Required ARIA child role not present: ${data.values}", "plural": "Required ARIA children role not present: ${data.values}", @@ -819,7 +822,7 @@ "pass": "List item has a
                ,
                  or role=\"list\" parent element", "fail": { "default": "List item does not have a
                    ,
                      parent element", - "roleNotValid": "List item does not have a
                        ,
                          parent element without a role, or a role=\"list\"" + "roleNotValid": "List item parent element has a role that is not role=\"list\"" } }, "only-dlitems": { @@ -862,7 +865,10 @@ "fail": "${data} on tag disables zooming on mobile devices" }, "target-offset": { - "pass": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "pass": { + "default": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "large": "Target far exceeds the minimum size of ${data.minOffset}px." + }, "fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.", "incomplete": { "default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?", @@ -872,7 +878,8 @@ "target-size": { "pass": { "default": "Control has sufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", - "obscured": "Control is ignored because it is fully obscured and thus not clickable" + "obscured": "Control is ignored because it is fully obscured and thus not clickable", + "large": "Target far exceeds the minimum size of ${data.minSize}px." }, "fail": { "default": "Target has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", @@ -882,7 +889,8 @@ "default": "Element with negative tabindex has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is this a target?", "contentOverflow": "Element size could not be accurately determined due to overflow content", "partiallyObscured": "Element with negative tabindex has insufficient size because it is partially obscured (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is this a target?", - "partiallyObscuredNonTabbable": "Target has insufficient size because it is partially obscured by a neighbor with negative tabindex (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is the neighbor a target?" + "partiallyObscuredNonTabbable": "Target has insufficient size because it is partially obscured by a neighbor with negative tabindex (smallest space is ${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px). Is the neighbor a target?", + "tooManyRects": "Could not get the target size because there are too many overlapping elements" } }, "header-present": { diff --git a/locales/da.json b/locales/da.json index 1b2e74e441..56c9a5ccd7 100644 --- a/locales/da.json +++ b/locales/da.json @@ -385,7 +385,9 @@ } }, "aria-required-children": { - "pass": "Krævet ARIA-under-elementer er til stede", + "pass": { + "default": "Krævet ARIA-under-elementer er til stede" + }, "fail": { "singular": "Krævet ARIA-under-elementers rolle er ikke til stede: ${data.values}", "plural": "Krævet ARIA-under-elements rolle er ikke til stede: ${data.values}" diff --git a/locales/de.json b/locales/de.json index 45304a0a55..cab4da906e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -17,10 +17,22 @@ "description": "Stellt sicher, dass der Wert des role-Attributes für dieses Element geeignet ist.", "help": "Der Wert des role-Attributes muss für dieses Element geeignet sein." }, + "aria-braille-equivalent": { + "description": "Stellt sicher, dass aria-braillelabel und aria-brailleroledescription ein non-braille Äquivalent haben.", + "help": "aria-braille Attribute müssen ein non-braille Äquivalent haben." + }, "aria-command-name": { "description": "Stellt sicher, dass jeder ARIA-button, -link und jedes -menuitem einen zugänglichen Namen (accessible name) hat.", "help": "ARIA Befehle müssen einen zugänglichen Namen (accessible name) besitzen." }, + "aria-conditional-attr": { + "description": "Stellt sicher, dass ARIA-Attribute wie in der Spezifikation der Rolle des Elements beschrieben verwendet werden.", + "help": "ARIA-Attribute müssen entsprechend der Rolle des Elements verwendet werden." + }, + "aria-deprecated-role": { + "description": "Stellt sicher, dass die Elemente keine veralteten Rollen verwenden.", + "help": "Veraltete ARIA-Rollen dürfen nicht verwendet werden." + }, "aria-dialog-name": { "description": "Stellt sicher, dass jeder ARIA-dialog und -alertdialog Knoten einen zugänglichen Namen (accessible name) hat.", "help": "ARIA-dialog und -alertdialog Knoten müssen einen zugänglichen Namen (accessible name) besitzen." @@ -45,6 +57,10 @@ "description": "Stellt sicher, dass jeder ARIA-progressbar Knoten einen zugänglichen Namen (accessible name) besitzt.", "help": "ARIA-progressbar Knoten müssen einen zugänglichen Namen (accessible name) besitzen." }, + "aria-prohibited-attr": { + "description": "Stellt sicher, dass ARIA-Attribute für die Rolle eines Elements nicht verboten sind.", + "help": "Elemente dürfen nur erlaubte ARIA-Attribute verwenden." + }, "aria-required-attr": { "description": "Stellt sicher, dass Elemente mit ARIA-Rollen alle erforderlichen ARIA-Attribute besitzen.", "help": "Erforderliche ARIA-Attribute müssen bereitgestellt werden." @@ -65,6 +81,10 @@ "description": "Stellt sicher, dass alle Elemente mit einer ARIA-Rolle auch einen gültigen Wert verwenden.", "help": "Verwendete ARIA-Rollen müssen gültigen Werten entsprechen." }, + "aria-text": { + "description": "Stellt sicher, dass role=\"text\" für Elemente verwendet wird, die keine fokussierbaren Nachkommen (descendants) haben.", + "help": "\"role=text\" sollte keine fokussierbaren Nachkommen (descendants) haben." + }, "aria-toggle-field-name": { "description": "Stellt sicher, dass jedes ARIA-toggle-Feld ein zugänglichen Namen (accessible name) besitzt.", "help": "ARIA-toggle-Felder benötigen einen zugänglichen Namen (accessible name)." @@ -109,14 +129,14 @@ "description": "Stellt sicher, dass jede Seite mindestens ein Mittel bereitstellt, welches dem Nutzer erlaubt direkt zum Inhalt der Seite zu springen.", "help": "Wiederholende Blöcke müssen vom Nutzer mit Hilfe von der Seite bereitgestellten Mitteln übersprungen werden können." }, - "color-contrast": { - "description": "Stellt sicher, dass der Kontrast zwischen Vorder- und Hintergrundfarbe den in der WCAG 2 als AA ausgewiesenen Kontrastgrenzwerten entspricht.", - "help": "Elemente müssen einen ausreichenden Farbkontrast haben." - }, "color-contrast-enhanced": { "description": "Stellt sicher, dass der Kontrast zwischen Vorder- und Hintergrundfarbe den in der WCAG 2 als AAA ausgewiesenen Kontrastgrenzwerten entspricht.", "help": "Elemente müssen einen ausreichenden Farbkontrast haben." }, + "color-contrast": { + "description": "Stellt sicher, dass der Kontrast zwischen Vorder- und Hintergrundfarbe den in der WCAG 2 als AA ausgewiesenen Kontrastgrenzwerten entspricht.", + "help": "Elemente müssen einen ausreichenden Farbkontrast haben." + }, "css-orientation-lock": { "description": "Stellt sicher, dass der Inhalt nicht nur auf einer sondern auf allen spezifischen Bildschirmausrichtungen angezeigt werden kann.", "help": "CSS Media Queries dürfen nicht genutzt werden um die Bildschirmausrichtung zu sperren." @@ -149,6 +169,10 @@ "description": "Stellt sicher, dass Überschriften einen wahrnehmbaren Text beinhalten.", "help": "Überschriften dürfen nichtleer sein." }, + "empty-table-header": { + "description": "Stellt sicher, dass Tabellenkopfzeilen einen wahrnehmbaren Text beinhalten.", + "help": "Tabellenkopfzeilen sollten nicht leer sein." + }, "focus-order-semantics": { "description": "Stellt sicher, dass Elemente in der Fokusreihenfolge eine geeignete Rolle besitzen.", "help": "Elemente in der Fokusreihenfolge benötigen eine Rolle, die für interaktive Elemente geeignet ist." @@ -157,6 +181,10 @@ "description": "Stellt sicher, dass ein form-Feld nur ein label-Element besitzt.", "help": "form-Felder sollten nur ein label-Element besitzen." }, + "frame-focusable-content": { + "description": "Stellt sicher, dass - und