-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #282 from github/kh-add-a11y
Add accessibility rule and React config
- Loading branch information
Showing
8 changed files
with
679 additions
and
113 deletions.
There are no files selected for viewing
This file contains 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 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,83 @@ | ||
# No Generic Link Text | ||
|
||
## Rule Details | ||
|
||
Avoid setting generic link text like, "Click here", "Read more", and "Learn more" which do not make sense when read out of context. | ||
|
||
Screen reader users often tab through links on a page to quickly find content without needing to listen to the full page. When link text is too generic, it becomes difficult to quickly identify the destination of the link. While it is possible to provide a more specific link text by setting the `aria-label`, this results in divergence between the label and the text and is not an ideal, future-proof solution. | ||
|
||
Additionally, generic link text can also problematic for heavy zoom users where the link context is out of view. | ||
|
||
Ensure that your link text is descriptive and the purpose of the link is clear even when read out of context of surrounding text. | ||
Learn more about how to write descriptive link text at [Access Guide: Write descriptive link text](https://www.accessguide.io/guide/descriptive-link-text) | ||
|
||
### Use of ARIA attributes | ||
|
||
If you _must_ use ARIA to replace the visible link text, include the visible text at the beginning. | ||
|
||
For example, on a pricing plans page, the following are good: | ||
|
||
- Visible text: `Learn more` | ||
- Accessible label: `Learn more about GitHub pricing plans` | ||
|
||
Accessible ✅ | ||
|
||
```html | ||
<a href="..." aria-label="Learn more about GitHub pricing plans">Learn more</a> | ||
``` | ||
|
||
Inaccessible 🚫 | ||
|
||
```html | ||
<a href="..." aria-label="GitHub pricing plans">Learn more</a> | ||
``` | ||
|
||
Including the visible text in the ARIA label satisfies [SC 2.5.3: Label in Name](https://www.w3.org/WAI/WCAG21/Understanding/label-in-name.html). | ||
|
||
#### False negatives | ||
|
||
Caution: because of the restrictions of static code analysis, we may not catch all violations. | ||
|
||
Please perform browser tests and spot checks: | ||
|
||
- when `aria-label` is set dynamically | ||
- when using `aria-labelledby` | ||
|
||
## Resources | ||
|
||
- [Primer: Links](https://primer.style/design/accessibility/links) | ||
- [Understanding Success Criterion 2.4.4: Link Purpose (In Context)](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html) | ||
- [WebAim: Links and Hypertext](https://webaim.org/techniques/hypertext/) | ||
- [Deque: Use link text that make sense when read out of context](https://dequeuniversity.com/tips/link-text) | ||
|
||
## Examples | ||
|
||
### **Incorrect** code for this rule 👎 | ||
|
||
```jsx | ||
<a href="github.com/about">Learn more</a> | ||
``` | ||
|
||
```jsx | ||
<a href="github.com/about">Read more</a> | ||
``` | ||
|
||
```jsx | ||
<a href="github.com/about" aria-label="Why dogs are awesome">Read more</a> | ||
``` | ||
|
||
```jsx | ||
<a href="github.com/about" aria-describedby="element123">Read more</a> | ||
``` | ||
|
||
### **Correct** code for this rule 👍 | ||
|
||
```jsx | ||
<a href="github.com/about">Learn more about GitHub</a> | ||
``` | ||
|
||
```jsx | ||
<a href="github.com/new">Create a new repository</a> | ||
``` | ||
|
||
## Version |
This file contains 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,10 @@ | ||
module.exports = { | ||
env: { | ||
browser: true | ||
}, | ||
plugins: ['github', 'jsx-a11y'], | ||
extends: ['plugin:jsx-a11y/recommended'], | ||
rules: { | ||
'github/a11y-no-generic-link-text': 'error' | ||
} | ||
} |
This file contains 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 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,68 @@ | ||
const {elementType, getProp, getPropValue} = require('jsx-ast-utils') | ||
|
||
const bannedLinkText = ['read more', 'here', 'click here', 'learn more', 'more'] | ||
|
||
/* Downcase and strip extra whitespaces and punctuation */ | ||
const stripAndDowncaseText = text => { | ||
return text | ||
.toLowerCase() | ||
.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, '') | ||
.replace(/\s{2,}/g, ' ') | ||
.trim() | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'disallow generic link text', | ||
url: require('../url')(module) | ||
}, | ||
schema: [] | ||
}, | ||
|
||
create(context) { | ||
return { | ||
JSXOpeningElement: node => { | ||
if (elementType(node) !== 'a') return | ||
if (getProp(node.attributes, 'aria-labelledby')) return | ||
|
||
let cleanTextContent // text content we can reliably fetch | ||
|
||
const parent = node.parent | ||
let jsxTextNode | ||
if (parent.children && parent.children.length > 0 && parent.children[0].type === 'JSXText') { | ||
jsxTextNode = parent.children[0] | ||
cleanTextContent = stripAndDowncaseText(parent.children[0].value) | ||
} | ||
|
||
const ariaLabel = getPropValue(getProp(node.attributes, 'aria-label')) | ||
const cleanAriaLabelValue = ariaLabel && stripAndDowncaseText(ariaLabel) | ||
|
||
if (ariaLabel) { | ||
if (bannedLinkText.includes(cleanAriaLabelValue)) { | ||
context.report({ | ||
node, | ||
message: | ||
'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' | ||
}) | ||
} | ||
if (cleanTextContent && !cleanAriaLabelValue.includes(cleanTextContent)) { | ||
context.report({ | ||
node, | ||
message: 'When using ARIA to set a more descriptive text, it must fully contain the visible label.' | ||
}) | ||
} | ||
} else { | ||
if (cleanTextContent) { | ||
if (!bannedLinkText.includes(cleanTextContent)) return | ||
context.report({ | ||
node: jsxTextNode, | ||
message: | ||
'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.