@codelaw/rules-html 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/NOTICE +24 -0
- package/README.md +166 -2
- package/codelaw-pack.toml +6 -0
- package/corpus.toml +76 -0
- package/data/html/elements.json +4892 -0
- package/data/html/entities.json +1 -0
- package/data/html/input-attributes.json +227 -0
- package/package.json +14 -4
- package/presets/html/a11y.toml +29 -0
- package/presets/html/conventions.toml +6 -0
- package/presets/html/document.toml +8 -0
- package/presets/html/recommended.toml +67 -0
- package/rules/html/area-alt.md +48 -0
- package/rules/html/aria-hidden-body.md +39 -0
- package/rules/html/aria-label-misuse.md +42 -0
- package/rules/html/attribute-allowed-values.cases.md +31 -0
- package/rules/html/attribute-allowed-values.md +41 -0
- package/rules/html/attribute-misuse.md +39 -0
- package/rules/html/autocomplete-password.md +41 -0
- package/rules/html/button-type.cases.md +28 -0
- package/rules/html/button-type.md +35 -0
- package/rules/html/close-attr.md +33 -0
- package/rules/html/close-order.cases.md +38 -0
- package/rules/html/close-order.md +47 -0
- package/rules/html/deprecated.md +39 -0
- package/rules/html/doctype-html.md +36 -0
- package/rules/html/element-name.cases.md +21 -0
- package/rules/html/element-name.md +42 -0
- package/rules/html/element-permitted-content.cases.md +54 -0
- package/rules/html/element-permitted-content.md +45 -0
- package/rules/html/element-permitted-occurrences.md +45 -0
- package/rules/html/element-permitted-order.md +44 -0
- package/rules/html/element-permitted-parent.cases.md +11 -0
- package/rules/html/element-permitted-parent.md +43 -0
- package/rules/html/element-required-ancestor.md +45 -0
- package/rules/html/element-required-attributes.md +44 -0
- package/rules/html/element-required-content.cases.md +23 -0
- package/rules/html/element-required-content.md +43 -0
- package/rules/html/empty-heading.md +40 -0
- package/rules/html/empty-title.md +39 -0
- package/rules/html/fieldset-legend.md +44 -0
- package/rules/html/form-dup-name.cases.md +38 -0
- package/rules/html/form-dup-name.md +48 -0
- package/rules/html/form-submit.md +44 -0
- package/rules/html/heading-level.cases.md +26 -0
- package/rules/html/heading-level.md +44 -0
- package/rules/html/hidden-focusable.cases.md +23 -0
- package/rules/html/hidden-focusable.md +40 -0
- package/rules/html/img-alt.cases.md +22 -0
- package/rules/html/img-alt.md +44 -0
- package/rules/html/img-title.md +40 -0
- package/rules/html/input-attributes.md +34 -0
- package/rules/html/input-image-alt.md +39 -0
- package/rules/html/input-missing-label.md +40 -0
- package/rules/html/link-text.md +39 -0
- package/rules/html/long-title.md +40 -0
- package/rules/html/map-dup-name.md +40 -0
- package/rules/html/map-id-name.md +38 -0
- package/rules/html/meta-refresh.md +42 -0
- package/rules/html/missing-doctype.cases.md +24 -0
- package/rules/html/missing-doctype.md +43 -0
- package/rules/html/multiple-labeled-controls.md +40 -0
- package/rules/html/no-abstract-role.md +39 -0
- package/rules/html/no-autoplay.md +37 -0
- package/rules/html/no-conditional-comment.md +39 -0
- package/rules/html/no-deprecated-attr.md +39 -0
- package/rules/html/no-dup-attr.md +37 -0
- package/rules/html/no-dup-class.md +33 -0
- package/rules/html/no-duplicate-id.cases.md +23 -0
- package/rules/html/no-duplicate-id.md +42 -0
- package/rules/html/no-implicit-close.cases.md +38 -0
- package/rules/html/no-implicit-close.md +40 -0
- package/rules/html/no-implicit-input-type.md +34 -0
- package/rules/html/no-inline-script.md +42 -0
- package/rules/html/no-inline-style.cases.md +12 -0
- package/rules/html/no-inline-style.md +42 -0
- package/rules/html/no-missing-references.md +39 -0
- package/rules/html/no-multiple-main.md +40 -0
- package/rules/html/no-raw-characters.cases.md +84 -0
- package/rules/html/no-raw-characters.md +35 -0
- package/rules/html/no-redundant-aria-label.md +36 -0
- package/rules/html/no-redundant-for.md +34 -0
- package/rules/html/no-redundant-role.md +42 -0
- package/rules/html/no-utf8-bom.md +45 -0
- package/rules/html/prefer-button.md +39 -0
- package/rules/html/prefer-native-element.md +40 -0
- package/rules/html/prefer-tbody.md +41 -0
- package/rules/html/require-sri.md +41 -0
- package/rules/html/script-element.md +36 -0
- package/rules/html/script-type.md +33 -0
- package/rules/html/target-blank-rel.cases.md +35 -0
- package/rules/html/target-blank-rel.md +32 -0
- package/rules/html/tel-non-breaking.md +37 -0
- package/rules/html/text-content.md +39 -0
- package/rules/html/th-scope.md +47 -0
- package/rules/html/unique-landmark.md +41 -0
- package/rules/html/unrecognized-char-ref.cases.md +41 -0
- package/rules/html/unrecognized-char-ref.md +42 -0
- package/rules/html/valid-autocomplete.cases.md +42 -0
- package/rules/html/valid-autocomplete.md +42 -0
- package/rules/html/valid-for.md +40 -0
- package/rules/html/valid-id.md +40 -0
- package/rules/html/void-content.md +34 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/element-required-ancestor
|
|
3
|
+
title: "Elements are inside the ancestor they need"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether to add the ancestor or use other elements depends on the intent"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/element-required-ancestor]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Elements that only work inside a given ancestor are there: `<area>` in a `<map>`, `<dt>` and `<dd>` in a `<dl>`.
|
|
13
|
+
|
|
14
|
+
**Why:** Outside it they have no meaning and are rendered as plain text, or not at all.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: html/element-required-ancestor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Wrap the elements in the ancestor the message names.
|
|
23
|
+
2. Or replace them with elements that make sense where they are.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<div>
|
|
33
|
+
<dt>Term</dt>
|
|
34
|
+
<dd>Definition</dd>
|
|
35
|
+
</div>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Do
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<dl>
|
|
42
|
+
<dt>Term</dt>
|
|
43
|
+
<dd>Definition</dd>
|
|
44
|
+
</dl>
|
|
45
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/element-required-attributes
|
|
3
|
+
title: "Elements have the attributes HTML requires"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the value can only come from the author"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/element-required-attributes, htmlhint/html-lang-require]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Elements have their required attributes: `src` on `<img>`, `lang` on `<html>`, `title` on `<iframe>`, `href` (or `imagesrcset`) on `<link>`, and so on.
|
|
13
|
+
|
|
14
|
+
**Why:** Without them the element does nothing useful: an `<img>` without `src` shows nothing, and an `<html>` without `lang` makes screen readers guess the language.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: html/element-required-attributes
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Add the attribute the message names, with the right value.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### HTML
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<html>
|
|
32
|
+
<head><title>Home</title></head>
|
|
33
|
+
<body><img alt="Logo"></body>
|
|
34
|
+
</html>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<head><title>Home</title></head>
|
|
42
|
+
<body><img src="logo.png" alt="Logo"></body>
|
|
43
|
+
</html>
|
|
44
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# html/element-required-content — cases
|
|
2
|
+
|
|
3
|
+
### case: an implied head does not count
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<body></body>
|
|
8
|
+
</html>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
1:1 html/element-required-content
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### case: a fragment head is checked too
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<head></head>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```expect
|
|
22
|
+
1:1 html/element-required-content
|
|
23
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/element-required-content
|
|
3
|
+
title: "Elements contain the children HTML requires"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the content of the missing child needs a human"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/element-required-content, htmlhint/title-require]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Elements that need a particular child have it: `<html>` has `<head>` and `<body>`, `<head>` has `<title>`, `<details>` has `<summary>`.
|
|
13
|
+
|
|
14
|
+
**Why:** A page without a `<title>` has no name in tabs, bookmarks and screen readers; a `<details>` without `<summary>` gets a generic label.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: html/element-required-content
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Add the child the message names, with real content.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### HTML
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="utf-8">
|
|
33
|
+
</head>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<head>
|
|
40
|
+
<meta charset="utf-8">
|
|
41
|
+
<title>Pricing</title>
|
|
42
|
+
</head>
|
|
43
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/empty-heading
|
|
3
|
+
title: "Headings have text"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the heading text needs a human"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/empty-heading]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Every `<h1>`–`<h6>` has text, or an image with `alt` text.
|
|
14
|
+
|
|
15
|
+
**Why:** Screen-reader users navigate by headings; an empty heading is a stop with nothing to announce.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/empty-heading
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Give the heading text, or remove it if it only made space (use CSS for that).
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<h2></h2>
|
|
33
|
+
<h2><span hidden>Pricing</span></h2>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<h2>Pricing</h2>
|
|
40
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/empty-title
|
|
3
|
+
title: "<title> has text"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the page title needs a human"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/empty-title]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The `<title>` element has text.
|
|
14
|
+
|
|
15
|
+
**Why:** The title names the page in tabs, history, bookmarks, search results and screen readers.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/empty-title
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Write a short title that says what the page is.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<title></title>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<title>Pricing — Acme</title>
|
|
39
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/fieldset-legend
|
|
3
|
+
title: "<fieldset> has a legend"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the group's name needs a human"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/wcag/h71]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A `<fieldset>` has a `<legend>` child.
|
|
14
|
+
|
|
15
|
+
**Why:** The legend names the group of controls; screen readers announce it with each control in the group.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/fieldset-legend
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Add a `<legend>` as the fieldset's first child, naming the group.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<fieldset>
|
|
33
|
+
<label><input type="radio" name="size" value="s"> Small</label>
|
|
34
|
+
</fieldset>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<fieldset>
|
|
41
|
+
<legend>Size</legend>
|
|
42
|
+
<label><input type="radio" name="size" value="s"> Small</label>
|
|
43
|
+
</fieldset>
|
|
44
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# html/form-dup-name — cases
|
|
2
|
+
|
|
3
|
+
### case: array names may repeat
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<form>
|
|
7
|
+
<input type="text" name="tags[]">
|
|
8
|
+
<input type="text" name="tags[]">
|
|
9
|
+
</form>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```expect
|
|
13
|
+
(none)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### case: separate forms are separate
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<form><input type="text" name="q"></form>
|
|
20
|
+
<form><input type="text" name="q"></form>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```expect
|
|
24
|
+
(none)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: disabled controls are not submitted
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<form>
|
|
31
|
+
<input type="text" name="q">
|
|
32
|
+
<input type="text" name="q" disabled>
|
|
33
|
+
</form>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```expect
|
|
37
|
+
(none)
|
|
38
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/form-dup-name
|
|
3
|
+
title: "Form controls have unique names"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "renaming a control changes what the server receives"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
allowArrayBrackets: { type: "boolean", default: true, description: "let `name[]` repeat" }
|
|
10
|
+
allowCheckboxDefault: { type: "boolean", default: true, description: "allow a hidden input before a checkbox of the same name" }
|
|
11
|
+
shared: { type: "string[]", default: ["radio", "button", "reset", "submit"], description: "control types that may share a name" }
|
|
12
|
+
ported_from: [html-validate/form-dup-name]
|
|
13
|
+
since: 0.1.0
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Within a form, no two controls share a `name`, except radio buttons of one group, buttons, and a hidden default before a checkbox. A `name[]` array name is not mixed with the plain `name`.
|
|
17
|
+
|
|
18
|
+
**Why:** The server receives both values under one name and usually keeps only one of them.
|
|
19
|
+
|
|
20
|
+
```codelaw
|
|
21
|
+
native: html/form-dup-name
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## How to fix
|
|
25
|
+
|
|
26
|
+
1. Give each control its own name, and update the code that reads the form.
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
### HTML
|
|
31
|
+
|
|
32
|
+
#### Don't
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<form>
|
|
36
|
+
<input type="text" name="email">
|
|
37
|
+
<input type="email" name="email">
|
|
38
|
+
</form>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Do
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<form>
|
|
45
|
+
<input type="text" name="name">
|
|
46
|
+
<input type="email" name="email">
|
|
47
|
+
</form>
|
|
48
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/form-submit
|
|
3
|
+
title: "Forms have a submit button"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the button's label and placement need a human"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/wcag/h32]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A `<form>` has a submit button, inside it or associated with it by `form=`.
|
|
14
|
+
|
|
15
|
+
**Why:** Without one there is no visible way to send the form; Enter-to-submit isn't obvious and doesn't work everywhere.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/form-submit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Add a `<button type="submit">` with a label that says what it does.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<form action="/search">
|
|
33
|
+
<input type="search" name="q" aria-label="Search">
|
|
34
|
+
</form>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<form action="/search">
|
|
41
|
+
<input type="search" name="q" aria-label="Search">
|
|
42
|
+
<button type="submit">Search</button>
|
|
43
|
+
</form>
|
|
44
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# html/heading-level — cases
|
|
2
|
+
|
|
3
|
+
### case: a dialog starts its own outline
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<h1>Page</h1>
|
|
7
|
+
<h2>Part</h2>
|
|
8
|
+
<dialog><h2>Confirm</h2><h3>Details</h3></dialog>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
(none)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### case: going back up any number of levels is fine
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<h1>a</h1>
|
|
19
|
+
<h2>b</h2>
|
|
20
|
+
<h3>c</h3>
|
|
21
|
+
<h2>d</h2>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```expect
|
|
25
|
+
(none)
|
|
26
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/heading-level
|
|
3
|
+
title: "Headings start at <h1> and go down one level at a time"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "changing a heading level changes its style and the outline"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
allowMultipleH1: { type: "boolean", default: false, description: "allow more than one `<h1>`" }
|
|
10
|
+
minInitialRank: { type: "string", default: "h1", description: "the lowest level the first heading may have (`h1`–`h6`, or `any`)" }
|
|
11
|
+
sectioningRoots: { type: "string[]", default: ["dialog", "[role=\"dialog\"]", "[role=\"alertdialog\"]"], description: "selectors of elements that start their own outline" }
|
|
12
|
+
ported_from: [html-validate/heading-level]
|
|
13
|
+
since: 0.1.0
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
The first heading is `<h1>`, there is one `<h1>`, and each heading is at most one level below the one before it. A `<dialog>` starts its own outline.
|
|
17
|
+
|
|
18
|
+
**Why:** Screen-reader users navigate the heading outline; a skipped level looks like missing content.
|
|
19
|
+
|
|
20
|
+
```codelaw
|
|
21
|
+
native: html/heading-level
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## How to fix
|
|
25
|
+
|
|
26
|
+
1. Change the heading to the level the message expects, and restyle it with CSS if it looked right before.
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
### HTML
|
|
31
|
+
|
|
32
|
+
#### Don't
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<h1>Guide</h1>
|
|
36
|
+
<h3>Install</h3>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<h1>Guide</h1>
|
|
43
|
+
<h2>Install</h2>
|
|
44
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# html/hidden-focusable — cases
|
|
2
|
+
|
|
3
|
+
### case: a disabled button can not take focus
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<button type="button" aria-hidden="true" disabled>x</button>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
(none)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: hidden by an ancestor
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<div aria-hidden="true">
|
|
17
|
+
<a href="/a">a</a>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```expect
|
|
22
|
+
2:3 html/hidden-focusable
|
|
23
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/hidden-focusable
|
|
3
|
+
title: "Focusable elements are not aria-hidden"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether to unhide it, take it out of the tab order or remove it depends on the design"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/hidden-focusable]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
An element keyboard focus can reach — a link, a form control, anything with `tabindex="0"` — is not `aria-hidden`, itself or through an ancestor.
|
|
14
|
+
|
|
15
|
+
**Why:** Keyboard users land on it while screen readers say nothing, so blind keyboard users reach a silent stop.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/hidden-focusable
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Remove `aria-hidden` if the element should be announced.
|
|
24
|
+
2. Otherwise take it out of the tab order with `tabindex="-1"`, or hide it from everyone with `hidden` or `inert`.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### HTML
|
|
29
|
+
|
|
30
|
+
#### Don't
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<a href="/home" aria-hidden="true">Home</a>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<a href="/home" aria-hidden="true" tabindex="-1">Home</a>
|
|
40
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# html/img-alt — cases
|
|
2
|
+
|
|
3
|
+
### case: a hidden image is not announced
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<img src="a.png" hidden>
|
|
7
|
+
<div aria-hidden="true"><img src="b.png"></div>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```expect
|
|
11
|
+
(none)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### case: an empty alt marks a decorative image
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<img src="a.png" alt="">
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```expect
|
|
21
|
+
(none)
|
|
22
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/img-alt
|
|
3
|
+
title: "<img> has alt text"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "only a human can describe the image, or decide it is decorative"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
allowEmpty: { type: "boolean", default: true, description: "accept `alt=\"\"` for decorative images" }
|
|
10
|
+
alias: { type: "string[]", default: [], description: "other attributes that count as alt text, such as a framework binding" }
|
|
11
|
+
ported_from: [html-validate/wcag/h37, htmlhint/alt-require]
|
|
12
|
+
tags: [a11y]
|
|
13
|
+
since: 0.1.0
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Every `<img>` has an `alt` attribute: a description, or `alt=""` for a purely decorative image.
|
|
17
|
+
|
|
18
|
+
**Why:** Screen readers read the file name of an image without `alt`; `alt` is also shown when the image fails to load.
|
|
19
|
+
|
|
20
|
+
```codelaw
|
|
21
|
+
native: html/img-alt
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## How to fix
|
|
25
|
+
|
|
26
|
+
1. Describe what the image shows, or what it is for, in `alt`.
|
|
27
|
+
2. If the image is decorative, write `alt=""`.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### HTML
|
|
32
|
+
|
|
33
|
+
#### Don't
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<img src="team.jpg">
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<img src="team.jpg" alt="The team at the 2024 offsite">
|
|
43
|
+
<img src="divider.png" alt="">
|
|
44
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/img-title
|
|
3
|
+
title: "Decorative images have no title"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether the image is decorative or informative is the author's call"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/wcag/h67]
|
|
9
|
+
tags: [a11y]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
An `<img>` with empty `alt` has no `title`.
|
|
14
|
+
|
|
15
|
+
**Why:** `alt=""` marks the image as decorative, while a `title` suggests it carries information; screen readers handle the mix inconsistently.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: html/img-title
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. If the image carries information, move the text from `title` to `alt`.
|
|
24
|
+
2. If it is decorative, remove the `title`.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### HTML
|
|
29
|
+
|
|
30
|
+
#### Don't
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<img src="logo.png" alt="" title="Acme">
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<img src="logo.png" alt="Acme">
|
|
40
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: html/input-attributes
|
|
3
|
+
title: "Input attributes fit the input type"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "scripts or attribute selectors may still read the attribute"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [html-validate/input-attributes]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `<input>` has only attributes its `type` uses: no `accept` on a text field, no `placeholder` on a checkbox, no `min` on an email field.
|
|
13
|
+
|
|
14
|
+
**Why:** The browser ignores them, so they mislead readers about how the field behaves.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: html/input-attributes
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### HTML
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<input type="text" name="photo" accept="image/png">
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Do
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<input type="text" name="photo">
|
|
34
|
+
```
|