Skip to content

Commit 01de64c

Browse files
committed
Added rule to require alt text on image
1 parent d322617 commit 01de64c

File tree

6 files changed

+122
-0
lines changed

6 files changed

+122
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Any use of third-party trademarks or logos are subject to those third-party's po
124124
| [dropdown-needs-labelling](docs/rules/dropdown-needs-labelling.md) | Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label | ✅ | | |
125125
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have label | ✅ | | |
126126
| [image-button-missing-aria](docs/rules/image-button-missing-aria.md) | Accessibility: Image buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby | ✅ | | |
127+
| [image-needs-alt](docs/rules/image-needs-alt.md) | Accessibility: Image must have alt attribute | ✅ | | |
127128
| [input-components-require-accessible-name](docs/rules/input-components-require-accessible-name.md) | Accessibility: Input fields must have accessible labelling: aria-label, aria-labelledby or an associated label | ✅ | | |
128129
| [link-missing-labelling](docs/rules/link-missing-labelling.md) | Accessibility: Image links must have an accessible name. Add either text content, labelling to the image or labelling to the link itself. | ✅ | | 🔧 |
129130
| [menu-item-needs-labelling](docs/rules/menu-item-needs-labelling.md) | Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby | ✅ | | |

docs/rules/image-needs-alt.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Accessibility: Image must have alt attribute (`@microsoft/fluentui-jsx-a11y/image-needs-alt`)
2+
3+
💼 This rule is enabled in the ✅ `recommended` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
## Rule details
8+
9+
This rule requires all `<Image>` components have non-empty alternative text. The `alt` attribute should provide a clear and concise text replacement for the image's content. It should *not* describe the presence of the image itself or the file name of the image.
10+
11+
12+
Examples of **incorrect** code for this rule:
13+
14+
```jsx
15+
<Image src="image.png" />
16+
```
17+
18+
```jsx
19+
<Image src="image.png" alt="" />
20+
```
21+
22+
```jsx
23+
<Image src="image.png" alt={null} />
24+
```
25+
26+
Examples of **correct** code for this rule:
27+
28+
```jsx
29+
<Image src="image.png" alt="A dog playing in a park." />
30+
```
31+
32+
## Further Reading
33+
34+
- [`<img>` Accessibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#accessibility)

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ module.exports = {
3131
"@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling": "error",
3232
"@microsoft/fluentui-jsx-a11y/field-needs-labelling": "error",
3333
"@microsoft/fluentui-jsx-a11y/image-button-missing-aria": "error",
34+
"@microsoft/fluentui-jsx-a11y/image-needs-alt": "error",
3435
"@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": "error",
3536
"@microsoft/fluentui-jsx-a11y/link-missing-labelling": "error",
3637
"@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": "error",
@@ -68,6 +69,7 @@ module.exports = {
6869
"dropdown-needs-labelling": rules.dropdownNeedsLabelling,
6970
"field-needs-labelling": rules.fieldNeedsLabelling,
7071
"image-button-missing-aria": rules.imageButtonMissingAria,
72+
"image-needs-alt": rules.imageNeedsAlt,
7173
"input-components-require-accessible-name": rules.inputComponentsRequireAccessibleName,
7274
"link-missing-labelling": rules.linkMissingLabelling,
7375
"menu-item-needs-labelling": rules.menuItemNeedsLabelling,

lib/rules/image-needs-alt.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
const elementType = require("jsx-ast-utils").elementType;
5+
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
6+
import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
7+
8+
//------------------------------------------------------------------------------
9+
// Rule Definition
10+
//------------------------------------------------------------------------------
11+
12+
const rule = ESLintUtils.RuleCreator.withoutDocs({
13+
meta: {
14+
type: "problem",
15+
docs: {
16+
description: "Accessibility: Image must have alt attribute",
17+
recommended: "error"
18+
},
19+
messages: {
20+
imageNeedsAlt: "Accessibility: Image must have alt attribute with a meaningful description of the image"
21+
},
22+
schema: []
23+
},
24+
defaultOptions: [], // no options needed
25+
create(context) {
26+
return {
27+
// Listen for variable declarations
28+
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
29+
// No error if the element is not an Image
30+
if (elementType(node) !== "Image") {
31+
return;
32+
}
33+
34+
// No error if alt prop exists and is non-empty
35+
if (hasNonEmptyProp(node.attributes, "alt")) {
36+
return;
37+
}
38+
39+
context.report({
40+
node,
41+
messageId: "imageNeedsAlt"
42+
});
43+
}
44+
};
45+
}
46+
});
47+
48+
export default rule;

lib/rules/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { default as dialogsurfaceNeedsAria } from "./dialogsurface-needs-aria";
1616
export { default as dropdownNeedsLabelling } from "./dropdown-needs-labelling";
1717
export { default as fieldNeedsLabelling } from "./field-needs-labelling";
1818
export { default as imageButtonMissingAria } from "./buttons/image-button-missing-aria";
19+
export { default as imageNeedsAlt } from "./image-needs-alt";
1920
export { default as inputComponentsRequireAccessibleName } from "./input-components-require-accessible-name";
2021
export { default as linkMissingLabelling } from "./link-missing-labelling";
2122
export { default as menuItemNeedsLabelling } from "./menu-item-needs-labelling";
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { Rule } from "eslint";
5+
import ruleTester from "./helper/ruleTester";
6+
import rule from "../../../lib/rules/image-needs-alt";
7+
8+
// -----------------------------------------------------------------------------
9+
// Tests
10+
// -----------------------------------------------------------------------------
11+
12+
ruleTester.run("image-needs-alt", rule as unknown as Rule.RuleModule, {
13+
valid: ['<Image src="image.png" alt="Description of image" />', '<Image src="image.png" alt={altText} />'],
14+
invalid: [
15+
{
16+
// No alt attribute
17+
code: '<Image src="image.png" />',
18+
errors: [{ messageId: "imageNeedsAlt" }]
19+
},
20+
{
21+
// Empty alt attribute
22+
code: '<Image src="image.png" alt="" />',
23+
errors: [{ messageId: "imageNeedsAlt" }]
24+
},
25+
{
26+
// Null alt attribute
27+
code: '<Image src="image.png" alt={null} />',
28+
errors: [{ messageId: "imageNeedsAlt" }]
29+
},
30+
{
31+
// Undefined alt attribute
32+
code: '<Image src="image.png" alt={undefined} />',
33+
errors: [{ messageId: "imageNeedsAlt" }]
34+
}
35+
]
36+
});

0 commit comments

Comments
 (0)