-
Notifications
You must be signed in to change notification settings - Fork 59
Adds svg-has-accessible-name
rule
#450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
7d3c8b1
adds test for svg-has-accessible-name rule
lindseywild 46fa4a6
updates text
lindseywild c2c7e07
add more valid test cases
lindseywild 4a59a5b
adds code for checking for accessible svg
lindseywild ce88c19
adds docs
lindseywild 485a104
adds to react preset
lindseywild c633deb
adds to index
lindseywild fc80126
Merge branch 'main' into lw/adds-svg-rule
lindseywild e77c35e
Apply suggestions from code review
lindseywild 40089c3
Merge branch 'main' into lw/adds-svg-rule
lindseywild File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# SVGs must have an accessible name (`github/a11y-svg-has-accessible-name`) | ||
|
||
💼 This rule is enabled in the ⚛️ `react` config. | ||
|
||
<!-- end auto-generated rule header --> | ||
|
||
## Rule Details | ||
|
||
An `<svg>` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element as the first child of the `<svg>` element. | ||
|
||
However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`. | ||
|
||
## Resources | ||
|
||
- [Accessible SVGs](https://css-tricks.com/accessible-svgs/) | ||
|
||
## Examples | ||
|
||
### **Incorrect** code for this rule 👎 | ||
|
||
```html | ||
<svg height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg height='100' width='100' title='Circle with a black outline and red fill'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
<title>Circle with a black outline and red fill</title> | ||
</svg> | ||
``` | ||
|
||
### **Correct** code for this rule 👍 | ||
|
||
```html | ||
<svg height='100' width='100'> | ||
<title>Circle with a black outline and red fill</title> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg aria-label='Circle with a black outline and red fill' height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg aria-labelledby='circle_text' height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg aria-hidden='true' height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
```html | ||
<svg role='presentation' height='100' width='100'> | ||
<circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/> | ||
</svg> | ||
``` | ||
|
||
## Version |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const {hasProp} = require('jsx-ast-utils') | ||
const {getElementType} = require('../utils/get-element-type') | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'SVGs must have an accessible name', | ||
url: require('../url')(module), | ||
}, | ||
schema: [], | ||
}, | ||
|
||
create(context) { | ||
return { | ||
JSXOpeningElement: node => { | ||
const elementType = getElementType(context, node) | ||
lindseywild marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (elementType !== 'svg') return | ||
|
||
// Check if there is a nested title element that is the first child of the `<svg>` | ||
const hasNestedTitleAsFirstChild = | ||
node.parent.children?.[0]?.type === 'JSXElement' && | ||
node.parent.children?.[0]?.openingElement?.name?.name === 'title' | ||
|
||
// Check if `aria-label` or `aria-labelledby` is set | ||
const hasAccessibleName = hasProp(node.attributes, 'aria-label') || hasProp(node.attributes, 'aria-labelledby') | ||
|
||
// Check if SVG is decorative | ||
const isDecorative = | ||
hasProp(node.attributes, 'role', 'presentation') || hasProp(node.attributes, 'aria-hidden', 'true') | ||
|
||
if (elementType === 'svg' && !hasAccessibleName && !isDecorative && !hasNestedTitleAsFirstChild) { | ||
context.report({ | ||
node, | ||
message: | ||
'`<svg>` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`.', | ||
}) | ||
} | ||
}, | ||
} | ||
}, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
const rule = require('../lib/rules/a11y-svg-has-accessible-name') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice tests!! thank you!! |
||
const RuleTester = require('eslint').RuleTester | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}) | ||
|
||
const errorMessage = | ||
'`<svg>` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`.' | ||
|
||
ruleTester.run('a11y-svg-has-accessible-name', rule, { | ||
valid: [ | ||
{ | ||
code: "<svg height='100' width='100'><title>Circle with a black outline and red fill</title><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
}, | ||
{ | ||
code: "<svg aria-label='Circle with a black outline and red fill' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
}, | ||
{ | ||
code: "<svg aria-labelledby='circle_text' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
}, | ||
{ | ||
code: "<svg aria-hidden='true' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
}, | ||
{ | ||
code: "<svg role='presentation' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
errors: [{message: errorMessage}], | ||
}, | ||
{ | ||
code: "<svg height='100' width='100' title='Circle with a black outline and red fill'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>", | ||
errors: [{message: errorMessage}], | ||
}, | ||
{ | ||
code: "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/><title>Circle with a black outline and red fill</title></svg>", | ||
errors: [{message: errorMessage}], | ||
}, | ||
], | ||
}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh I didn't consider
role="presentation"
! TIL!