@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.
Files changed (104) hide show
  1. package/LICENSE +23 -0
  2. package/NOTICE +24 -0
  3. package/README.md +166 -2
  4. package/codelaw-pack.toml +6 -0
  5. package/corpus.toml +76 -0
  6. package/data/html/elements.json +4892 -0
  7. package/data/html/entities.json +1 -0
  8. package/data/html/input-attributes.json +227 -0
  9. package/package.json +14 -4
  10. package/presets/html/a11y.toml +29 -0
  11. package/presets/html/conventions.toml +6 -0
  12. package/presets/html/document.toml +8 -0
  13. package/presets/html/recommended.toml +67 -0
  14. package/rules/html/area-alt.md +48 -0
  15. package/rules/html/aria-hidden-body.md +39 -0
  16. package/rules/html/aria-label-misuse.md +42 -0
  17. package/rules/html/attribute-allowed-values.cases.md +31 -0
  18. package/rules/html/attribute-allowed-values.md +41 -0
  19. package/rules/html/attribute-misuse.md +39 -0
  20. package/rules/html/autocomplete-password.md +41 -0
  21. package/rules/html/button-type.cases.md +28 -0
  22. package/rules/html/button-type.md +35 -0
  23. package/rules/html/close-attr.md +33 -0
  24. package/rules/html/close-order.cases.md +38 -0
  25. package/rules/html/close-order.md +47 -0
  26. package/rules/html/deprecated.md +39 -0
  27. package/rules/html/doctype-html.md +36 -0
  28. package/rules/html/element-name.cases.md +21 -0
  29. package/rules/html/element-name.md +42 -0
  30. package/rules/html/element-permitted-content.cases.md +54 -0
  31. package/rules/html/element-permitted-content.md +45 -0
  32. package/rules/html/element-permitted-occurrences.md +45 -0
  33. package/rules/html/element-permitted-order.md +44 -0
  34. package/rules/html/element-permitted-parent.cases.md +11 -0
  35. package/rules/html/element-permitted-parent.md +43 -0
  36. package/rules/html/element-required-ancestor.md +45 -0
  37. package/rules/html/element-required-attributes.md +44 -0
  38. package/rules/html/element-required-content.cases.md +23 -0
  39. package/rules/html/element-required-content.md +43 -0
  40. package/rules/html/empty-heading.md +40 -0
  41. package/rules/html/empty-title.md +39 -0
  42. package/rules/html/fieldset-legend.md +44 -0
  43. package/rules/html/form-dup-name.cases.md +38 -0
  44. package/rules/html/form-dup-name.md +48 -0
  45. package/rules/html/form-submit.md +44 -0
  46. package/rules/html/heading-level.cases.md +26 -0
  47. package/rules/html/heading-level.md +44 -0
  48. package/rules/html/hidden-focusable.cases.md +23 -0
  49. package/rules/html/hidden-focusable.md +40 -0
  50. package/rules/html/img-alt.cases.md +22 -0
  51. package/rules/html/img-alt.md +44 -0
  52. package/rules/html/img-title.md +40 -0
  53. package/rules/html/input-attributes.md +34 -0
  54. package/rules/html/input-image-alt.md +39 -0
  55. package/rules/html/input-missing-label.md +40 -0
  56. package/rules/html/link-text.md +39 -0
  57. package/rules/html/long-title.md +40 -0
  58. package/rules/html/map-dup-name.md +40 -0
  59. package/rules/html/map-id-name.md +38 -0
  60. package/rules/html/meta-refresh.md +42 -0
  61. package/rules/html/missing-doctype.cases.md +24 -0
  62. package/rules/html/missing-doctype.md +43 -0
  63. package/rules/html/multiple-labeled-controls.md +40 -0
  64. package/rules/html/no-abstract-role.md +39 -0
  65. package/rules/html/no-autoplay.md +37 -0
  66. package/rules/html/no-conditional-comment.md +39 -0
  67. package/rules/html/no-deprecated-attr.md +39 -0
  68. package/rules/html/no-dup-attr.md +37 -0
  69. package/rules/html/no-dup-class.md +33 -0
  70. package/rules/html/no-duplicate-id.cases.md +23 -0
  71. package/rules/html/no-duplicate-id.md +42 -0
  72. package/rules/html/no-implicit-close.cases.md +38 -0
  73. package/rules/html/no-implicit-close.md +40 -0
  74. package/rules/html/no-implicit-input-type.md +34 -0
  75. package/rules/html/no-inline-script.md +42 -0
  76. package/rules/html/no-inline-style.cases.md +12 -0
  77. package/rules/html/no-inline-style.md +42 -0
  78. package/rules/html/no-missing-references.md +39 -0
  79. package/rules/html/no-multiple-main.md +40 -0
  80. package/rules/html/no-raw-characters.cases.md +84 -0
  81. package/rules/html/no-raw-characters.md +35 -0
  82. package/rules/html/no-redundant-aria-label.md +36 -0
  83. package/rules/html/no-redundant-for.md +34 -0
  84. package/rules/html/no-redundant-role.md +42 -0
  85. package/rules/html/no-utf8-bom.md +45 -0
  86. package/rules/html/prefer-button.md +39 -0
  87. package/rules/html/prefer-native-element.md +40 -0
  88. package/rules/html/prefer-tbody.md +41 -0
  89. package/rules/html/require-sri.md +41 -0
  90. package/rules/html/script-element.md +36 -0
  91. package/rules/html/script-type.md +33 -0
  92. package/rules/html/target-blank-rel.cases.md +35 -0
  93. package/rules/html/target-blank-rel.md +32 -0
  94. package/rules/html/tel-non-breaking.md +37 -0
  95. package/rules/html/text-content.md +39 -0
  96. package/rules/html/th-scope.md +47 -0
  97. package/rules/html/unique-landmark.md +41 -0
  98. package/rules/html/unrecognized-char-ref.cases.md +41 -0
  99. package/rules/html/unrecognized-char-ref.md +42 -0
  100. package/rules/html/valid-autocomplete.cases.md +42 -0
  101. package/rules/html/valid-autocomplete.md +42 -0
  102. package/rules/html/valid-for.md +40 -0
  103. package/rules/html/valid-id.md +40 -0
  104. 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
+ ```