Ludochaordic
Fantaisies programatico-ludiques

A review of HTML linters

... and how to use them in CI pipelines.

HTML Doctype - Author: Seobility - License: CC BY-SA 4.0

Overview

HTML linters: W3C v.Nu checker html-tidy htmlhint html-validate LintHTML html-eslint
Age 20 years > 22 years 11 years 7 years 5 years (fork of an 11 years old project) 5 years
Actively maintained?
Reported issues 1361
(79% closed)
816
(77% closed)
299
(85% closed)
297
(82% closed)
69
(73% closed)
104
(84% closed)
Number of rules >❔< (hundreds) 337 37 104 65 50
Written in Java C Typescript Typescript Typescript Javascript
Version tested 24.5.11 5.9.14 1.1.4 9.5.0 0.10.1 0.36.0
Fast?
Auto-fix? only 2 rules
Allow custom rules / plugins
Spot useful errors?

The last criterion was estimated based on the tests performed in section "Evaluating HTML linters on reference pages" below in this article.

Introduction / motivation

I have started writing this blog article 4 years ago, but never took the time to finish it until now.

Clearly, this is all about obsessive-compulsive static code analysis 😅, because checking HTML syntax is less necessary nowadays:

  • when building websites, it has become common to use higher-level languages that get converted to "clean" HTML by intermediate tools: Markdown, reStructuredText, wiki markup, etc. Or even JSX/TSX templates for frontend developers. It's less frequent to write "bare" HTML directly.
  • HTML parsers are very robust to errors: your current browser is perfectly able to render horribly broken HTML like this.

This other article dives more into this subject: Why is HTML linting not a common practice? @dev.to.

Now, we will have a look at the major open-source HTML linters, and then analyse how they compare to each other.

HTML linters and their usage

W3C v.Nu HTML checker

v.Nu, the Nu HTML Checker is maintained by the World Wide Web Consortium (W3C). While it's running on Java, it is usable as Docker image or even directly as a binary.

Errors & warnings can be selectively ignored using a .vnurc file, cf. their "Message filtering" wiki page.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:
  - apt update -y && apt install -y curl unzip  # assume Debian distribution
  - |
    curl -ROLs https://github.com/validator/validator/releases/download/latest/vnu.linux.zip \
    && unzip vnu.linux.zip \
    && rm vnu.linux.zip \
    && vnu-runtime-image/bin/vnu --version
  script:
  - vnu-runtime-image/bin/vnu --filterfile .vnurc --Werror --skip-non-html root/html/dir/

Sadly, they do not perform frequent GitHub releases, they only override a latest version: https://github.com/validator/validator/releases/. This makes it difficult to "pin" a given recent version of this linter in a CI pipeline.

html-tidy

I think that html-tidy is the oldest HTML linter 🫡.

Sadly, it seems unmaintained: the latest binaries are from 4 years ago.

It has an interesting "auto-fix" feature (with -modify), making it the only linter I know able to auto-format HTML code.

The latest version allows to mute some warnings, but the exit code will still be non-zero, making its usage in CI pipelines difficult.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:  # assume Debian distribution
  - wget -O tidy-html5.deb https://github.com/htacg/tidy-html5/releases/download/5.8.0/tidy-5.8.0-Linux-64bit.deb \
    && dpkg -i tidy-html5.deb \ \
    && rm tidy-html5.deb
    && tidy -version
  script:
  - tidy -errors -config htmltidy.conf *.html

Example of htmltidy.conf:

// cf. https://api.html-tidy.org/tidy/quickref_next.html
char-encoding: utf8
newline: lf
indent: auto
indent-spaces: 4
wrap: no
drop-empty-elements: false
warn-proprietary-attributes: no
# Display message ID's with error reports, useful to filter them out using "mute":
mute-id: yes

htmlhint

htmlhint is a more recent, JavaScript-based alternative.

It is relatively basic and straightforward to use, but contains a relatively low number of HTML rules implemented.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:  # assume npm installed
  - npm install -g htmlhint
  script:
  - htmlhint

Example of .htmlhintrc:

{
    "tagname-lowercase": true,
    "attr-lowercase": true,
    "attr-value-double-quotes": true,
    "doctype-first": true,
    "tag-pair": true,
    "spec-char-escape": true,
    "id-unique": true,
    "src-not-empty": true,
    "attr-no-duplication": true,
    "title-require": true
}

html-validate

html-validate is a serious contender when it comes to recent & Javascript-based alternatives.

In my humble opinion, it's the best among the "fast" linters, and comes second overall after the W3C v.Nu HTML checker.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:  # assume npm installed
  - npm install -g html-validate
  script:
  - html-validate

Example of .htmlvalidate.json:

{
  "$schema": "https://html-validate.org/schemas/config.json",
  "extends": ["html-validate:recommended"],
  "rules": {
    "unique-landmark": "off"
  }
}

LintHTML

LintHTML is a fork of the unmaintained htmllint project.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:  # assume npm installed
  - npm install -g @linthtml/linthtml
  script:
  - linthtml

linthtml --init should generate a starting point .linthtmlrc file.

⚠️ However I already spotted some issue with the config parser 😅

html-eslint

html-eslint is an ESLint plugin for formatting and linting HTML.

It doesn't contain many rules, and based on my tests mostly report indentation errors.

As an example, the following code snippet installs & runs it in a Gitlab CI pipeline:

  install:  # assume npm installed
  - npm install -D -g eslint
  - npm install -D @html-eslint/parser @html-eslint/eslint-plugin
  script:
  - eslint *.html

Evaluating HTML linters on reference pages

First, I tried to evaluate how much HTML checkers can be "picky" and produce "unnecessary" warnings by default, when checking some somehow "standard" web pages.

All the HTML checkers considered provide a way to "silence" those warnings using a configuration file, so even the "noisiest" HTML linter can be "tamed shut" 😄

The table below indicates how many rules are "triggered" by the HTML Linters considered, when evaluating some reference HTML pages:

HTML linters: W3C v.Nu checker html-tidy htmlhint html-validate LintHTML html-eslint
google.com 6 12 1 9 6 6
youtube.com 6 4 2 3 9 5
MDN Learning Area 3 1 2 2
Bootstrap album example 2 2 8 3
Foundation web framework 3 2 4 4 5

Analysis details:

google.com
$ wget https://www.google.com -O google.com.html

$ vnu --Werror google.com.html
5.335-5.344: error: CSS: “display”: “inline-box” is not a “display” value.
10.410-10.410: error: Malformed byte sequence: “e9”.
10.923-10.923: error: Malformed byte sequence: “e8”.
7.909-7.929: error: The “bgcolor” attribute on the “body” element is obsolete. Use CSS instead.
10.43-10.48: error: Element “nobr” not allowed as child of element “div” in this context. (Suppressing further errors from this subtree.)
10.684-10.708: error: Attribute “width” not allowed on element “div” at this point.
10.709-10.714: error: Element “nobr” not allowed as child of element “div” in this context. (Suppressing further errors from this subtree.)
10.1180-10.1187: error: The “center” element is obsolete. Use CSS instead.
10.1188-10.1213: error: The “clear” attribute on the “br” element is obsolete. Use CSS instead.
10.1443-10.1481: error: The “cellpadding” attribute on the “table” element is obsolete. Use CSS instead.
10.1443-10.1481: error: The “cellspacing” attribute on the “table” element is obsolete. Use CSS instead.
10.1482-10.1498: error: The “valign” attribute on the “tr” element is obsolete. Use CSS instead.
10.1499-10.1514: error: The “width” attribute on the “td” element is obsolete. Use CSS instead.
10.1526-10.1554: error: The “align” attribute on the “td” element is obsolete. Use CSS instead.
10.1526-10.1554: error: The “nowrap” attribute on the “td” element is obsolete. Use CSS instead.
11.272-11.272: error: Malformed byte sequence: “e9”.
11.1080-11.1080: error: Malformed byte sequence: “e9”.
11.1162-11.1162: error: Malformed byte sequence: “c0”.
11.154-11.208: error: The “align” attribute on the “td” element is obsolete. Use CSS instead.
11.154-11.208: error: The “nowrap” attribute on the “td” element is obsolete. Use CSS instead.
11.154-11.208: error: The “width” attribute on the “td” element is obsolete. Use CSS instead.
11.960-11.987: error: Element “div” not allowed as child of element “span” in this context. (Suppressing further errors from this subtree.)
11.1444-11.1444: error: Malformed byte sequence: “e9”.
11.1340-11.1378: error: Element “p” not allowed as child of element “span” in this context. (Suppressing further errors from this subtree.)

$ tidy -errors google.com.html
line 10 column 30 - Info: value for attribute "id" missing quote marks
line 10 column 49 - Info: value for attribute "class" missing quote marks
line 10 column 76 - Info: value for attribute "class" missing quote marks
line 10 column 129 - Warning: unescaped & or unknown entity "&tab"
line 10 column 149 - Info: value for attribute "class" missing quote marks
line 10 column 201 - Warning: unescaped & or unknown entity "&tab"
line 10 column 219 - Info: value for attribute "class" missing quote marks
line 10 column 268 - Warning: unescaped & or unknown entity "&tab"
line 10 column 286 - Info: value for attribute "class" missing quote marks
line 10 column 350 - Info: value for attribute "class" missing quote marks
line 10 column 410 - Warning: replacing invalid UTF-8 bytes (char. code U+0009)
line 10 column 417 - Info: value for attribute "class" missing quote marks
line 10 column 484 - Info: value for attribute "class" missing quote marks
line 10 column 547 - Info: value for attribute "class" missing quote marks
line 10 column 684 - Info: value for attribute "id" missing quote marks
line 10 column 684 - Info: value for attribute "width" missing quote marks
line 10 column 715 - Info: value for attribute "id" missing quote marks
line 10 column 715 - Info: value for attribute "class" missing quote marks
line 10 column 745 - Info: value for attribute "id" missing quote marks
line 10 column 745 - Info: value for attribute "class" missing quote marks
line 10 column 775 - Info: value for attribute "id" missing quote marks
line 10 column 795 - Info: value for attribute "class" missing quote marks
line 10 column 878 - Info: value for attribute "class" missing quote marks
line 10 column 923 - Warning: replacing invalid UTF-8 bytes (char. code U+0008)
line 10 column 935 - Info: value for attribute "target" missing quote marks
line 10 column 935 - Info: value for attribute "id" missing quote marks
line 10 column 1011 - Warning: unescaped & or unknown entity "&passive"
line 10 column 1024 - Warning: unescaped & or unknown entity "&continue"
line 10 column 1057 - Warning: unescaped & or unknown entity "&ec"
line 10 column 935 - Info: value for attribute "class" missing quote marks
line 10 column 1105 - Info: value for attribute "class" missing quote marks
line 10 column 1105 - Info: value for attribute "style" missing quote marks
line 10 column 1139 - Info: value for attribute "class" missing quote marks
line 10 column 1139 - Info: value for attribute "style" missing quote marks
line 11 column 272 - Warning: replacing invalid UTF-8 bytes (char. code U+0009)
line 11 column 942 - Warning: missing </span> before <div>
line 11 column 988 - Warning: inserting implicit <span>
line 11 column 988 - Warning: missing </span> before <div>
line 11 column 1048 - Warning: inserting implicit <span>
line 11 column 1080 - Warning: replacing invalid UTF-8 bytes (char. code U+0009)
line 11 column 1162 - Warning: replacing invalid UTF-8 bytes (char. code U+0000)
line 11 column 1379 - Warning: inserting implicit <span>
line 11 column 1444 - Warning: replacing invalid UTF-8 bytes (char. code U+0009)
line 11 column 1505 - Warning: discarding unexpected </span>
line 7 column 909 - Warning: <body> attribute "bgcolor" has invalid value "#fff"
line 11 column 988 - Warning: Implicit <span> anchor "footer" duplicated by Tidy.
line 11 column 1048 - Warning: Implicit <span> anchor "footer" duplicated by Tidy.
line 11 column 1379 - Warning: Implicit <span> anchor "footer" duplicated by Tidy.
line 7 column 909 - Warning: <body> attribute "bgcolor" not allowed for HTML5
line 10 column 1180 - Warning: <center> element removed from HTML5
line 10 column 1526 - Warning: <td> attribute "align" not allowed for HTML5
line 11 column 154 - Warning: <td> attribute "align" not allowed for HTML5
line 1 column 270 - Warning: <script> proprietary attribute "nonce"
line 5 column 1070 - Warning: <script> proprietary attribute "nonce"
line 7 column 909 - Warning: <body> attribute "bgcolor" not allowed for HTML5
line 7 column 930 - Warning: <script> proprietary attribute "nonce"
line 10 column 43 - Warning: <nobr> is not approved by W3C
line 10 column 684 - Warning: <div> proprietary attribute "width"
line 10 column 709 - Warning: <nobr> is not approved by W3C
line 10 column 1188 - Warning: <br> attribute "clear" not allowed for HTML5
line 10 column 1443 - Warning: <table> attribute "cellpadding" not allowed for HTML5
line 10 column 1443 - Warning: <table> attribute "cellspacing" not allowed for HTML5
line 10 column 1482 - Warning: <tr> attribute "valign" not allowed for HTML5
line 10 column 1499 - Warning: <td> attribute "width" not allowed for HTML5
line 10 column 1526 - Warning: <td> attribute "align" not allowed for HTML5
line 10 column 1526 - Warning: <td> attribute "nowrap" not allowed for HTML5
line 10 column 2279 - Warning: <script> proprietary attribute "nonce"
line 11 column 154 - Warning: <td> attribute "align" not allowed for HTML5
line 11 column 154 - Warning: <td> attribute "nowrap" not allowed for HTML5
line 11 column 154 - Warning: <td> attribute "width" not allowed for HTML5
line 11 column 347 - Warning: <script> proprietary attribute "nonce"
line 11 column 1521 - Warning: <script> proprietary attribute "nonce"
line 11 column 2042 - Warning: <script> proprietary attribute "nonce"
line 11 column 2833 - Warning: <script> proprietary attribute "nonce"

$ htmlhint google.com.html
L10 |})();</script><div id="mngb"><div id=gbar><nobr><b class=gb1>Recherche</b> <a class=gb1 href="https:/...
                                      ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...ipt><div id="mngb"><div id=gbar><nobr><b class=gb1>Recherche</b> <a class=gb1 href="https://www.googl...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...gbar><nobr><b class=gb1>Recherche</b> <a class=gb1 href="https://www.google.com/imghp?hl=fr&tab=wi">I...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...le.com/imghp?hl=fr&tab=wi">Images</a> <a class=gb1 href="https://maps.google.fr/maps?hl=fr&tab=wl">Ma...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...google.fr/maps?hl=fr&tab=wl">Maps</a> <a class=gb1 href="https://play.google.com/?hl=fr&tab=w8">Play<...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...ay.google.com/?hl=fr&tab=w8">Play</a> <a class=gb1 href="https://www.youtube.com/?tab=w1">YouTube</a>...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |.../www.youtube.com/?tab=w1">YouTube</a> <a class=gb1 href="https://news.google.com/?tab=wn">Actualités<...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...ws.google.com/?tab=wn">Actualités</a> <a class=gb1 href="https://mail.google.com/mail/?tab=wm">Gmail<...
                                                  ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...il.google.com/mail/?tab=wm">Gmail</a> <a class=gb1 href="https://drive.google.com/?tab=wo">Drive</a> ...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...//drive.google.com/?tab=wo">Drive</a> <a class=gb1 style="text-decoration:none" href="https://www.goo...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...<u>Plus</u> »</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><spa...
                                                ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...u> »</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf ...
                                                ^ The value of attribute [ width ] must be in double quotes. (attr-value-double-quotes)
L10 |...div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span ...
                                                ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...v id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe>...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://...
                                                ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...pan id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://www.goo...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://www.google.fr/history/optout?h...
                                                ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...tp://www.google.fr/history/optout?hl=fr" class=gb4>Historique Web</a> | <a  href="/preferences?hl=fr"...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |... Web</a> | <a  href="/preferences?hl=fr" class=gb4>Paramètres</a> | <a target=_top id=gb_70 href="htt...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...ces?hl=fr" class=gb4>Paramètres</a> | <a target=_top id=gb_70 href="https://accounts.google.com/Servi...
                                                  ^ The value of attribute [ target ] must be in double quotes. (attr-value-double-quotes)
L10 |...lass=gb4>Paramètres</a> | <a target=_top id=gb_70 href="https://accounts.google.com/ServiceLogin?hl=f...
                                                  ^ The value of attribute [ id ] must be in double quotes. (attr-value-double-quotes)
L10 |...tinue=https://www.google.com/&ec=GAZAAQ" class=gb4>Connexion</a></nobr></div><div class=gbh style=lef...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...class=gb4>Connexion</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...Connexion</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div>...
                                                ^ The value of attribute [ style ] must be in double quotes. (attr-value-double-quotes)
L10 |...v><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div><center><br clear="all" ...
                                                ^ The value of attribute [ class ] must be in double quotes. (attr-value-double-quotes)
L10 |...ss=gbh style=left:0></div><div class=gbh style=right:0></div></div><center><br clear="all" id="lgpd">...
                                                ^ The value of attribute [ style ] must be in double quotes. (attr-value-double-quotes)

$ html-validate google.com.html
 1:1     error  DOCTYPE should be uppercase                                                  doctype-style
 7:915   error  Attribute "bgcolor" is deprecated on <body> element                          no-deprecated-attr
10:35    error  Attribute "id" using unquoted value                                          attr-quotes
10:44    error  <nobr> is deprecated: use CSS instead                                        deprecated
10:44    error  <nobr> element is not permitted as content under <div>                       element-permitted-content
10:52    error  Attribute "class" using unquoted value                                       attr-quotes
10:79    error  Attribute "class" using unquoted value                                       attr-quotes
10:152   error  Attribute "class" using unquoted value                                       attr-quotes
10:222   error  Attribute "class" using unquoted value                                       attr-quotes
10:289   error  Attribute "class" using unquoted value                                       attr-quotes
10:353   error  Attribute "class" using unquoted value                                       attr-quotes
10:420   error  Attribute "class" using unquoted value                                       attr-quotes
10:487   error  Attribute "class" using unquoted value                                       attr-quotes
10:550   error  Attribute "class" using unquoted value                                       attr-quotes
10:560   error  Inline style is not allowed                                                  no-inline-style
10:689   error  Attribute "id" using unquoted value                                          attr-quotes
10:698   error  Attribute "width" using unquoted value                                       attr-quotes
10:710   error  <nobr> is deprecated: use CSS instead                                        deprecated
10:710   error  <nobr> element is not permitted as content under <div>                       element-permitted-content
10:721   error  Attribute "id" using unquoted value                                          attr-quotes
10:728   error  Attribute "class" using unquoted value                                       attr-quotes
10:751   error  Attribute "id" using unquoted value                                          attr-quotes
10:758   error  Attribute "class" using unquoted value                                       attr-quotes
10:781   error  Attribute "id" using unquoted value                                          attr-quotes
10:847   error  Attribute "class" using unquoted value                                       attr-quotes
10:908   error  Attribute "class" using unquoted value                                       attr-quotes
10:938   error  Attribute "target" using unquoted value                                      attr-quotes
10:950   error  Attribute "id" using unquoted value                                          attr-quotes
10:1069  error  Attribute "class" using unquoted value                                       attr-quotes
10:1110  error  Attribute "class" using unquoted value                                       attr-quotes
10:1120  error  Attribute "style" using unquoted value                                       attr-quotes
10:1120  error  Inline style is not allowed                                                  no-inline-style
10:1144  error  Attribute "class" using unquoted value                                       attr-quotes
10:1154  error  Attribute "style" using unquoted value                                       attr-quotes
10:1154  error  Inline style is not allowed                                                  no-inline-style
10:1181  error  <center> is deprecated: use CSS instead                                      deprecated
10:1181  error  <center> element is not permitted as content under <body>                    element-permitted-content
10:1192  error  Attribute "clear" is deprecated on <br> element                              no-deprecated-attr
10:1345  error  Inline style is not allowed                                                  no-inline-style
10:1450  error  Attribute "cellpadding" is deprecated on <table> element                     no-deprecated-attr
10:1466  error  Attribute "cellspacing" is deprecated on <table> element                     no-deprecated-attr
10:1483  error  Prefer to wrap <tr> elements in <tbody>                                      prefer-tbody
10:1486  error  Attribute "valign" is deprecated on <tr> element                             no-deprecated-attr
10:1503  error  Attribute "width" is deprecated on <td> element                              no-deprecated-attr
10:1530  error  Attribute "align" is deprecated on <td> element                              no-deprecated-attr
10:1545  error  Attribute "nowrap" is deprecated on <td> element                             no-deprecated-attr
10:1773  error  Inline style is not allowed                                                  no-inline-style
10:1807  error  <input> is missing recommended "type" attribute                              no-implicit-input-type
10:1825  error  Inline style is not allowed                                                  no-inline-style
10:1993  error  Inline style is not allowed                                                  no-inline-style
10:2113  error  Prefer to use <button> instead of <input type="submit"> when adding buttons  prefer-button
10:2271  error  Prefer to use <button> instead of <input type="submit"> when adding buttons  prefer-button
11:174   error  Attribute "align" is deprecated on <td> element                              no-deprecated-attr
11:187   error  Attribute "nowrap" is deprecated on <td> element                             no-deprecated-attr
11:197   error  Attribute "width" is deprecated on <td> element                              no-deprecated-attr
11:893   error  Inline style is not allowed                                                  no-inline-style
11:961   error  <div> element is not permitted as content under <span>                       element-permitted-content
11:965   error  Inline style is not allowed                                                  no-inline-style
11:993   error  Inline style is not allowed                                                  no-inline-style
11:1341  error  <p> element is not permitted as content under <span>                         element-permitted-content
11:1343  error  Inline style is not allowed                                                  no-inline-style

$ linthtml google.com.html
 13:1804  error    Incorrect indentation for `html` beginning at L13:C1804. Expected `<\html>` to be at an indentation of 0 but was found at 1803.      indent-style
  1:82    error    Incorrect indentation for `head` beginning at L1:C82. Expected `<head>` to be at an indentation of 4 but was found at 81.            indent-style
  1:88    error    Incorrect indentation for `meta` beginning at L1:C88. Expected `<meta>` to be at an indentation of 8 but was found at 87.            indent-style
  4:479   error    Incorrect indentation for `script` beginning at L4:C479. Expected `<\script>` to be at an indentation of 8 but was found at 478.     indent-style
  5:1     error    Incorrect indentation for `style` beginning at L5:C1. Expected `<\style>` to be at an indentation of 8 but was found at 0.           indent-style
  4:488   error    The tag <style> is banned and should not be used
                            tag-bans
  5:9     error    The tag <style> is banned and should not be used
                            tag-bans
  7:893   error    Incorrect indentation for `script` beginning at L7:C893. Expected `<\script>` to be at an indentation of 8 but was found at 892.     indent-style
  7:915   error    The attribute "bgcolor" attribute is cannot be used as it's banned
                            attr-bans
 13:1797  error    Incorrect indentation for `body` beginning at L13:C1797. Expected `<\body>` to be at an indentation of 4 but was found at 1796.      indent-style
  7:930   error    Incorrect indentation for `script` beginning at L7:C930. Expected `<script>` to be at an indentation of 8 but was found at 929.      indent-style
 10:38    error    The attribute "id" is not double quoted
                            attr-quote-style
 10:49    error    The tag <b> is banned and should not be used
                            tag-bans
 10:58    error    The attribute "class" is not double quoted
                            attr-quote-style
 10:85    error    The attribute "class" is not double quoted
                            attr-quote-style
 10:158   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:228   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:295   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:359   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:426   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:493   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:560   error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:556   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:698   error    The attribute "width" attribute is cannot be used as it's banned
                            attr-bans
 10:692   error    The attribute "id" is not double quoted
                            attr-quote-style
 10:704   error    The attribute "width" is not double quoted
                            attr-quote-style
 10:724   error    The attribute "id" is not double quoted
                            attr-quote-style
 10:734   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:754   error    The attribute "id" is not double quoted
                            attr-quote-style
 10:764   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:784   error    The attribute "id" is not double quoted
                            attr-quote-style
 10:853   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:914   error    The attribute "class" is not double quoted
                            attr-quote-style
 10:945   error    The attribute "target" is not double quoted
                            attr-quote-style
 10:953   error    The attribute "id" is not double quoted
                            attr-quote-style
 10:1075  error    The attribute "class" is not double quoted
                            attr-quote-style
 10:1120  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:1116  error    The attribute "class" is not double quoted
                            attr-quote-style
 10:1126  error    The attribute "style" is not double quoted
                            attr-quote-style
 10:1154  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:1150  error    The attribute "class" is not double quoted
                            attr-quote-style
 10:1160  error    The attribute "style" is not double quoted
                            attr-quote-style
 11:1512  error    Incorrect indentation for `center` beginning at L11:C1512. Expected `<\center>` to be at an indentation of 8 but was found at 1511.  indent-style
 10:1188  error    Incorrect indentation for `br` beginning at L10:C1188. Expected `<br>` to be at an indentation of 12 but was found at 1187.          indent-style
 10:1219  error    The value "XjhHGf" of attribute "id" does not respect the format: underscore
                            id-class-style
 10:1345  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:1373  error    The attribute "width" attribute is cannot be used as it's banned
                            attr-bans
 11:881   error    Incorrect indentation for `form` beginning at L11:C881. Expected `<\form>` to be at an indentation of 12 but was found at 880.       indent-style
 11:288   error    Incorrect indentation for `table` beginning at L11:C288. Expected `<\table>` to be at an indentation of 16 but was found at 287.     indent-style
 11:283   error    Incorrect indentation for `tr` beginning at L11:C283. Expected `<\tr>` to be at an indentation of 20 but was found at 282.           indent-style
 10:1503  error    The attribute "width" attribute is cannot be used as it's banned
                            attr-bans
 10:1499  error    Incorrect indentation for `td` beginning at L10:C1499. Expected `<td>` to be at an indentation of 24 but was found at 1498.          indent-style
 10:1530  error    The attribute "align" attribute is cannot be used as it's banned
                            attr-bans
 11:149   error    Incorrect indentation for `td` beginning at L11:C149. Expected `<\td>` to be at an indentation of 24 but was found at 148.           indent-style
 10:1555  error    Incorrect indentation for `input` beginning at L10:C1555. Expected `<input>` to be at an indentation of 28 but was found at 1554.    indent-style
 10:1773  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:1825  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 10:1913  error    The attribute "value" requires a value
                            attr-req-value
 10:1993  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 11:142   error    Incorrect indentation for `span` beginning at L11:C142. Expected `<\span>` to be at an indentation of 28 but was found at 141.       indent-style
 11:135   error    Incorrect indentation for `span` beginning at L11:C135. Expected `<\span>` to be at an indentation of 32 but was found at 134.       indent-style
 10:2171  error    Incorrect indentation for `input` beginning at L10:C2171. Expected `<input>` to be at an indentation of 36 but was found at 2170.    indent-style
 10:2190  error    The value "tsuid_Zy3YZ83uLebYkdUP0fCjoAw_1" of attribute "id" does not respect the format: underscore
                            id-class-style
 11:38    error    Incorrect indentation for `script` beginning at L11:C38. Expected `<\script>` to be at an indentation of 36 but was found at 37.     indent-style
 11:174   error    The attribute "align" attribute is cannot be used as it's banned
                            attr-bans
 11:197   error    The attribute "width" attribute is cannot be used as it's banned
                            attr-bans
 11:893   error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 11:965   error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 11:993   error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 11:1036  error    The value "WqQANb" of attribute "id" does not respect the format: underscore
                            id-class-style
 11:1343  error    The attribute "style" attribute is cannot be used as it's banned
                            attr-bans
 13:1781  error    Incorrect indentation for `script` beginning at L13:C1781. Expected `<\script>` to be at an indentation of 8 but was found at 1780.  indent-style

$ eslint google.com.html
...redacted for brevity...
youtube.com
$ wget https://youtube.com -O youtube.com.html

$ vnu --Werror youtube.com.html
1.16-1.195: error: Attribute “darker-dark-theme” not allowed on element “html” at this point.
1.16-1.195: error: Attribute “darker-dark-theme-deprecate” not allowed on element “html” at this point.
1.16-1.195: error: Attribute “system-icons” not allowed on element “html” at this point.
1.16-1.195: error: Attribute “typography” not allowed on element “html” at this point.
1.16-1.195: error: Attribute “typography-spacing” not allowed on element “html” at this point.
1.16-1.195: error: Attribute “refresh” not allowed on element “html” at this point.
1.686-1.739: info: Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
1.740-1.1023: error: Bad value “origin-trial” for attribute “http-equiv” on element “meta”.
1.740-1.1023: info: Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
12.728-12.796: error: Bad value “handheld” for attribute “media” on element “link”: The media “handheld” has been deprecated
21.417-21.473: error: Attribute “name” not allowed on element “script” at this point.
21.25747-21.25895: error: Attribute “autocorrect” not allowed on element “input” at this point.
21.25980-21.26033: error: Attribute “viewBox” not allowed on element “g” at this point.
21.30451-21.30472: error: Duplicate ID “youtube-paths”.
21.27048-21.27069: info warning: The first occurrence of ID “youtube-paths” was here.
23.15-23.210: error: Attribute “name” not allowed on element “link” at this point.
23.3336-23.3350: error: Duplicate ID “menu”.
21.25980-21.26033: info warning: The first occurrence of ID “menu” was here.

$ tidy -errors youtube.com.html
line 21 column 337 - Warning: unescaped & or unknown entity "&family"
line 21 column 371 - Warning: unescaped & or unknown entity "&display"
line 21 column 25502 - Error: <ytd-app> is not recognized! Did you mean to enable the custom-tags option?
line 21 column 25502 - Warning: discarding unexpected <ytd-app>
line 21 column 25511 - Error: <ytd-masthead> is not recognized! Did you mean to enable the custom-tags option?
line 21 column 25511 - Warning: discarding unexpected <ytd-masthead>
line 21 column 32909 - Warning: discarding unexpected </ytd-masthead>
line 21 column 34720 - Warning: discarding unexpected </ytd-app>
line 1 column 16 - Warning: <html> proprietary attribute "darker-dark-theme"
line 1 column 16 - Warning: <html> proprietary attribute "darker-dark-theme-deprecate"
line 1 column 16 - Warning: <html> proprietary attribute "system-icons"
line 1 column 16 - Warning: <html> proprietary attribute "typography"
line 1 column 16 - Warning: <html> proprietary attribute "typography-spacing"
line 1 column 16 - Warning: <html> proprietary attribute "refresh"
line 1 column 202 - Warning: <script> proprietary attribute "nonce"
line 1 column 1024 - Warning: <script> proprietary attribute "nonce"
line 2 column 153 - Warning: <script> proprietary attribute "nonce"
line 11 column 10 - Warning: <script> proprietary attribute "nonce"
line 12 column 1487 - Warning: <script> proprietary attribute "nonce"
line 12 column 1643 - Warning: <script> proprietary attribute "nonce"
line 17 column 10 - Warning: <script> proprietary attribute "nonce"
line 17 column 139 - Warning: <script> proprietary attribute "nonce"
line 17 column 310 - Warning: <script> proprietary attribute "nonce"
line 17 column 475 - Warning: <script> proprietary attribute "nonce"
line 17 column 618 - Warning: <script> proprietary attribute "nonce"
line 17 column 783 - Warning: <script> proprietary attribute "nonce"
line 17 column 887 - Warning: <script> proprietary attribute "nonce"
line 20 column 10 - Warning: <script> proprietary attribute "nonce"
line 20 column 114 - Warning: <script> proprietary attribute "nonce"
line 20 column 247 - Warning: <script> proprietary attribute "nonce"
line 20 column 404 - Warning: <script> proprietary attribute "nonce"
line 20 column 525 - Warning: <script> proprietary attribute "nonce"
line 21 column 10 - Warning: <script> proprietary attribute "nonce"
line 21 column 139 - Warning: <script> proprietary attribute "nonce"
line 21 column 242 - Warning: <link> proprietary attribute "nonce"
line 21 column 417 - Warning: <script> proprietary attribute "name"
line 21 column 417 - Warning: <script> proprietary attribute "nonce"
line 21 column 615 - Warning: <link> proprietary attribute "nonce"
line 21 column 766 - Warning: <link> proprietary attribute "nonce"
line 21 column 893 - Warning: <link> proprietary attribute "nonce"
line 21 column 1097 - Warning: <style> proprietary attribute "nonce"
line 21 column 1424 - Warning: <style> proprietary attribute "nonce"
line 21 column 4559 - Warning: <style> proprietary attribute "nonce"
line 21 column 5413 - Warning: <style> proprietary attribute "nonce"
line 21 column 6312 - Warning: <style> proprietary attribute "nonce"
line 21 column 6837 - Warning: <script> proprietary attribute "nonce"
line 21 column 6940 - Warning: <script> proprietary attribute "nonce"
line 21 column 24741 - Warning: <script> proprietary attribute "nonce"
line 21 column 24867 - Warning: <script> proprietary attribute "nonce"
line 21 column 24969 - Warning: <script> proprietary attribute "nonce"
line 21 column 25080 - Warning: <script> proprietary attribute "nonce"
line 21 column 25747 - Warning: <input> proprietary attribute "autocapitalize"
line 21 column 25747 - Warning: <input> proprietary attribute "autocorrect"
line 21 column 44980 - Warning: <script> proprietary attribute "nonce"
line 21 column 45083 - Warning: <script> proprietary attribute "nonce"
line 21 column 45188 - Warning: <script> proprietary attribute "nonce"
line 21 column 45296 - Warning: <script> proprietary attribute "nonce"
line 21 column 45401 - Warning: <script> proprietary attribute "nonce"
line 21 column 45607 - Warning: <script> proprietary attribute "nonce"
line 21 column 45715 - Warning: <script> proprietary attribute "nonce"
line 21 column 45823 - Warning: <script> proprietary attribute "nonce"
line 21 column 45927 - Warning: <script> proprietary attribute "nonce"
line 21 column 46029 - Warning: <script> proprietary attribute "nonce"
line 21 column 46361 - Warning: <script> proprietary attribute "nonce"
line 21 column 82820 - Warning: <script> proprietary attribute "nonce"
line 23 column 15 - Warning: <link> proprietary attribute "name"
line 23 column 15 - Warning: <link> proprietary attribute "nonce"
line 23 column 4056 - Warning: <script> proprietary attribute "nonce"
line 23 column 4159 - Warning: <script> proprietary attribute "nonce"
line 23 column 4304 - Warning: <script> proprietary attribute "nonce"

$ htmlhint youtube.com.html
L21 |... height="18" viewBox="0 0 104 18"><defs><clipPath id="clip0_161_5084"><rect width="104" height="18"/>...
                                                ^ The html element name of [ clipPath ] must be in lowercase. (tagname-lowercase)
L21 |...61_5084"><rect width="104" height="18"/></clipPath></defs><g clip-path="url(#clip0_161_5084)"><path d...
                                                ^ The html element name of [ clipPath ] must be in lowercase. (tagname-lowercase)
L21 |... height="18" viewBox="0 0 111 18"><defs><clipPath id="clip0_161_5078"><rect width="111" height="18"/>...
                                                ^ The html element name of [ clipPath ] must be in lowercase. (tagname-lowercase)
L21 |...61_5078"><rect width="111" height="18"/></clipPath></defs><g clip-path="url(#clip0_161_5078)"><path d...
                                                ^ The html element name of [ clipPath ] must be in lowercase. (tagname-lowercase)
L21 |...18" stroke="grey" stroke-width="0.5"/><g id="youtube-paths"><path d="M30.125 0.570001V17.57H32.915V11...
                                                ^ The id value [ youtube-paths ] must be unique. (id-unique)
L23 |...or"></div><div class="flex-1"></div><div id="menu"><div class="menu-button skeleton-bg-color"></div><...
                                                ^ The id value [ menu ] must be unique. (id-unique)

$ html-validate youtube.com.html
 1:22     error  Inline style is not allowed                                              no-inline-style
 1:738    error  Expected omitted end tag <meta> instead of self-closing element <meta/>  void-style
 1:1022   error  Expected omitted end tag <meta> instead of self-closing element <meta/>  void-style
21:25183  error  <iframe> is missing required "title" attribute                           element-required-attributes
21:34920  error  Inline style is not allowed                                              no-inline-style
21:35340  error  Inline style is not allowed                                              no-inline-style
21:35760  error  Inline style is not allowed                                              no-inline-style
21:36180  error  Inline style is not allowed                                              no-inline-style
21:36600  error  Inline style is not allowed                                              no-inline-style
21:37020  error  Inline style is not allowed                                              no-inline-style
21:37440  error  Inline style is not allowed                                              no-inline-style
21:37860  error  Inline style is not allowed                                              no-inline-style
21:38280  error  Inline style is not allowed                                              no-inline-style
21:38700  error  Inline style is not allowed                                              no-inline-style
21:39120  error  Inline style is not allowed                                              no-inline-style
21:39540  error  Inline style is not allowed                                              no-inline-style
21:39960  error  Inline style is not allowed                                              no-inline-style
21:40380  error  Inline style is not allowed                                              no-inline-style
21:40800  error  Inline style is not allowed                                              no-inline-style
21:41220  error  Inline style is not allowed                                              no-inline-style
21:41640  error  Inline style is not allowed                                              no-inline-style
21:42060  error  Inline style is not allowed                                              no-inline-style
21:42480  error  Inline style is not allowed                                              no-inline-style
21:42900  error  Inline style is not allowed                                              no-inline-style
21:43320  error  Inline style is not allowed                                              no-inline-style
21:43740  error  Inline style is not allowed                                              no-inline-style
21:44160  error  Inline style is not allowed                                              no-inline-style
21:44580  error  Inline style is not allowed                                              no-inline-style

$ linthtml youtube.com.html
...redacted for brevity...

$ eslint youtube.com.html
...redacted for brevity...
MDN Learning Area
$ vnu --Werror mdn-learning-area/html/forms/datetime-local-picker-fallback/index.html
1.1-4.86: error: Non-space characters found without seeing a doctype first. Expected “<!DOCTYPE html>”.
1.1-4.86: error: Element “head” is missing a required instance of child element “title”.
1.1-4.86: info warning: Consider adding a “lang” attribute to the “html” start tag to declare the language of this document.

$ tidy -errors mdn-learning-area/html/forms/datetime-local-picker-fallback/index.html
line 29 column 7 - Warning: trimming empty <span>

$ html-validate mdn-learning-area/html/forms/datetime-local-picker-fallback/index.html
 1:1  error  DOCTYPE should be uppercase               doctype-style
25:4  error  <form> element must have a submit button  wcag/h32

$ eslint mdn-learning-area/html/forms/datetime-local-picker-fallback/index.html
...redacted for brevity...
Bootstrap album example
$ tidy -errors bootstrap-5.0.2-examples/album/index.html
line 64 column 9 - Warning: trimming empty <span>
line 63 column 7 - Warning: trimming empty <button>
line 91 column 13 - Warning: <svg> proprietary attribute "focusable"
line 107 column 13 - Warning: <svg> proprietary attribute "focusable"
line 123 column 13 - Warning: <svg> proprietary attribute "focusable"
line 140 column 13 - Warning: <svg> proprietary attribute "focusable"
line 156 column 13 - Warning: <svg> proprietary attribute "focusable"
line 172 column 13 - Warning: <svg> proprietary attribute "focusable"
line 189 column 13 - Warning: <svg> proprietary attribute "focusable"
line 205 column 13 - Warning: <svg> proprietary attribute "focusable"
line 221 column 13 - Warning: <svg> proprietary attribute "focusable"

$ htmlhint bootstrap-5.0.2-examples/album/index.html
 1:1  error  DOCTYPE should be uppercase  doctype-style
 13:1  error  Trailing whitespace         no-trailing-whitespace
 34:1  error  Trailing whitespace         no-trailing-whitespace
 37:1  error  Trailing whitespace         no-trailing-whitespace
254:1  error  Trailing whitespace         no-trailing-whitespace

$ linthtml bootstrap-5.0.2-examples/album/index.html
...redacted for brevity...

$ eslint bootstrap-5.0.2-examples/album/index.html
...redacted for brevity...
Foundation web framework
$ vnu --Werror foundation-sites-templates/index.html
4.5-4.28: info: Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
5.5-5.76: info: Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
39.11-39.68: error: An “img” element must have an “alt” attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
51.11-51.68: error: An “img” element must have an “alt” attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
63.11-63.68: error: An “img” element must have an “alt” attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
75.11-75.68: error: An “img” element must have an “alt” attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
107.7-107.71: error: Bad value “navigation” for attribute “role” on element “ul”.

$ tidy -errors foundation-sites-templates/index.html
line 39 column 11 - Warning: <img> lacks "alt" attribute
line 51 column 11 - Warning: <img> lacks "alt" attribute
line 63 column 11 - Warning: <img> lacks "alt" attribute
line 75 column 11 - Warning: <img> lacks "alt" attribute
line 113 column 9 - Warning: trimming empty <li>

$ htmlhint foundation-sites-templates/index.html
 1:1   error  DOCTYPE should be uppercase                                              doctype-style
 4:27  error  Expected omitted end tag <meta> instead of self-closing element <meta/>  void-style
 5:75  error  Expected omitted end tag <meta> instead of self-closing element <meta/>  void-style
 39:12  error  <img> is missing required "alt" attribute                               wcag/h37
 51:12  error  <img> is missing required "alt" attribute                               wcag/h37
 63:12  error  <img> is missing required "alt" attribute                               wcag/h37
 75:12  error  <img> is missing required "alt" attribute                               wcag/h37
107:30  error  Prefer to use the native <nav> element                                  prefer-native-element

$ linthtml foundation-sites-templates/index.html
  2:13  error    The value "no-js" of attribute "class" does not respect the format: underscore                                                  id-class-style
  3:3   error    Incorrect indentation for `head` beginning at L3:C3. Expected `<head>` to be at an indentation of 4 but was found at 2.         indent-style
  4:5   error    Incorrect indentation for `meta` beginning at L4:C5. Expected `<meta>` to be at an indentation of 8 but was found at 4.         indent-style
  5:5   error    Incorrect indentation for `meta` beginning at L5:C5. Expected `<meta>` to be at an indentation of 8 but was found at 4.         indent-style
  6:5   error    Incorrect indentation for `title` beginning at L6:C5. Expected `<title>` to be at an indentation of 8 but was found at 4.       indent-style
  7:5   error    Incorrect indentation for `link` beginning at L7:C5. Expected `<link>` to be at an indentation of 8 but was found at 4.         indent-style
  9:3   error    Incorrect indentation for `body` beginning at L9:C3. Expected `<body>` to be at an indentation of 4 but was found at 2.         indent-style
 11:5   error    Incorrect indentation for `Comment` beginning at L11:C5. Expected `<Comment>` to be at an indentation of 8 but was found at 4.  indent-style
 12:5   error    Incorrect indentation for `div` beginning at L12:C5. Expected `<div>` to be at an indentation of 8 but was found at 4.          indent-style
 12:16  error    The value "top-bar" of attribute "class" does not respect the format: underscore                                                id-class-style
 13:7   error    Incorrect indentation for `div` beginning at L13:C7. Expected `<div>` to be at an indentation of 12 but was found at 6.         indent-style
 13:18  error    The value "top-bar-left" of attribute "class" does not respect the format: underscore                                           id-class-style
 14:9   error    Incorrect indentation for `ul` beginning at L14:C9. Expected `<ul>` to be at an indentation of 16 but was found at 8.           indent-style
 15:11  error    Incorrect indentation for `li` beginning at L15:C11. Expected `<li>` to be at an indentation of 20 but was found at 10.         indent-style
 15:21  error    The value "menu-text" of attribute "class" does not respect the format: underscore                                              id-class-style
 18:7   error    Incorrect indentation for `div` beginning at L18:C7. Expected `<div>` to be at an indentation of 12 but was found at 6.         indent-style
 18:18  error    The value "top-bar-right" of attribute "class" does not respect the format: underscore                                          id-class-style
 19:9   error    Incorrect indentation for `ul` beginning at L19:C9. Expected `<ul>` to be at an indentation of 16 but was found at 8.           indent-style
 20:11  error    Incorrect indentation for `li` beginning at L20:C11. Expected `<li>` to be at an indentation of 20 but was found at 10.         indent-style
 21:11  error    Incorrect indentation for `li` beginning at L21:C11. Expected `<li>` to be at an indentation of 20 but was found at 10.         indent-style
 22:11  error    Incorrect indentation for `li` beginning at L22:C11. Expected `<li>` to be at an indentation of 20 but was found at 10.         indent-style
 23:11  error    Incorrect indentation for `li` beginning at L23:C11. Expected `<li>` to be at an indentation of 20 but was found at 10.         indent-style
 27:5   error    Incorrect indentation for `Comment` beginning at L27:C5. Expected `<Comment>` to be at an indentation of 8 but was found at 4.  indent-style
 29:5   error    Incorrect indentation for `div` beginning at L29:C5. Expected `<div>` to be at an indentation of 8 but was found at 4.          indent-style
 30:7   error    Incorrect indentation for `div` beginning at L30:C7. Expected `<div>` to be at an indentation of 12 but was found at 6.         indent-style
 30:18  error    The value "text-center" of attribute "class" does not respect the format: underscore                                            id-class-style
 31:9   error    Incorrect indentation for `h1` beginning at L31:C9. Expected `<h1>` to be at an indentation of 16 but was found at 8.           indent-style
 35:5   error    Incorrect indentation for `div` beginning at L35:C5. Expected `<div>` to be at an indentation of 8 but was found at 4.          indent-style
 36:7   error    Incorrect indentation for `div` beginning at L36:C7. Expected `<div>` to be at an indentation of 12 but was found at 6.         indent-style
 36:18  error    The value "medium-8" of attribute "class" does not respect the format: underscore                                               id-class-style
 37:9   error    Incorrect indentation for `div` beginning at L37:C9. Expected `<div>` to be at an indentation of 16 but was found at 8.         indent-style
 37:20  error    The value "blog-post" of attribute "class" does not respect the format: underscore                                              id-class-style
 38:11  error    Incorrect indentation for `h3` beginning at L38:C11. Expected `<h3>` to be at an indentation of 20 but was found at 10.         indent-style
 39:11  error    Incorrect indentation for `img` beginning at L39:C11. Expected `<img>` to be at an indentation of 20 but was found at 10.       indent-style
 39:11  error    The "alt" attribute must be set for <img> tag                                                                                   img-req-alt
 40:11  error    Incorrect indentation for `p` beginning at L40:C11. Expected `<p>` to be at an indentation of 20 but was found at 10.           indent-style
 41:11  error    Incorrect indentation for `div` beginning at L41:C11. Expected `<div>` to be at an indentation of 20 but was found at 10.       indent-style
 42:13  error    Incorrect indentation for `ul` beginning at L42:C13. Expected `<ul>` to be at an indentation of 24 but was found at 12.         indent-style
 43:15  error    Incorrect indentation for `li` beginning at L43:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 44:15  error    Incorrect indentation for `li` beginning at L44:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 49:9   error    Incorrect indentation for `div` beginning at L49:C9. Expected `<div>` to be at an indentation of 16 but was found at 8.         indent-style
 49:20  error    The value "blog-post" of attribute "class" does not respect the format: underscore                                              id-class-style
 50:11  error    Incorrect indentation for `h3` beginning at L50:C11. Expected `<h3>` to be at an indentation of 20 but was found at 10.         indent-style
 51:11  error    Incorrect indentation for `img` beginning at L51:C11. Expected `<img>` to be at an indentation of 20 but was found at 10.       indent-style
 51:11  error    The "alt" attribute must be set for <img> tag                                                                                   img-req-alt
 52:11  error    Incorrect indentation for `p` beginning at L52:C11. Expected `<p>` to be at an indentation of 20 but was found at 10.           indent-style
 53:11  error    Incorrect indentation for `div` beginning at L53:C11. Expected `<div>` to be at an indentation of 20 but was found at 10.       indent-style
 54:13  error    Incorrect indentation for `ul` beginning at L54:C13. Expected `<ul>` to be at an indentation of 24 but was found at 12.         indent-style
 55:15  error    Incorrect indentation for `li` beginning at L55:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 56:15  error    Incorrect indentation for `li` beginning at L56:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 61:9   error    Incorrect indentation for `div` beginning at L61:C9. Expected `<div>` to be at an indentation of 16 but was found at 8.         indent-style
 61:20  error    The value "blog-post" of attribute "class" does not respect the format: underscore                                              id-class-style
 62:11  error    Incorrect indentation for `h3` beginning at L62:C11. Expected `<h3>` to be at an indentation of 20 but was found at 10.         indent-style
 63:11  error    Incorrect indentation for `img` beginning at L63:C11. Expected `<img>` to be at an indentation of 20 but was found at 10.       indent-style
 63:11  error    The "alt" attribute must be set for <img> tag                                                                                   img-req-alt
 64:11  error    Incorrect indentation for `p` beginning at L64:C11. Expected `<p>` to be at an indentation of 20 but was found at 10.           indent-style
 65:11  error    Incorrect indentation for `div` beginning at L65:C11. Expected `<div>` to be at an indentation of 20 but was found at 10.       indent-style
 66:13  error    Incorrect indentation for `ul` beginning at L66:C13. Expected `<ul>` to be at an indentation of 24 but was found at 12.         indent-style
 67:15  error    Incorrect indentation for `li` beginning at L67:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 68:15  error    Incorrect indentation for `li` beginning at L68:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 73:9   error    Incorrect indentation for `div` beginning at L73:C9. Expected `<div>` to be at an indentation of 16 but was found at 8.         indent-style
 73:20  error    The value "blog-post" of attribute "class" does not respect the format: underscore                                              id-class-style
 74:11  error    Incorrect indentation for `h3` beginning at L74:C11. Expected `<h3>` to be at an indentation of 20 but was found at 10.         indent-style
 75:11  error    Incorrect indentation for `img` beginning at L75:C11. Expected `<img>` to be at an indentation of 20 but was found at 10.       indent-style
 75:11  error    The "alt" attribute must be set for <img> tag                                                                                   img-req-alt
 76:11  error    Incorrect indentation for `p` beginning at L76:C11. Expected `<p>` to be at an indentation of 20 but was found at 10.           indent-style
 77:11  error    Incorrect indentation for `div` beginning at L77:C11. Expected `<div>` to be at an indentation of 20 but was found at 10.       indent-style
 78:13  error    Incorrect indentation for `ul` beginning at L78:C13. Expected `<ul>` to be at an indentation of 24 but was found at 12.         indent-style
 79:15  error    Incorrect indentation for `li` beginning at L79:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 80:15  error    Incorrect indentation for `li` beginning at L80:C15. Expected `<li>` to be at an indentation of 28 but was found at 14.         indent-style
 85:7   error    Incorrect indentation for `div` beginning at L85:C7. Expected `<div>` to be at an indentation of 12 but was found at 6.         indent-style
 85:37  error    The attribute "data-sticky-container" requires a value                                                                          attr-req-value
 85:18  error    The value "medium-3" of attribute "class" does not respect the format: underscore                                               id-class-style
 86:9   error    Incorrect indentation for `div` beginning at L86:C9. Expected `<div>` to be at an indentation of 16 but was found at 8.         indent-style
 86:29  error    The attribute "data-sticky" requires a value                                                                                    attr-req-value
 87:11  error    Incorrect indentation for `h4` beginning at L87:C11. Expected `<h4>` to be at an indentation of 20 but was found at 10.         indent-style
 88:11  error    Incorrect indentation for `ul` beginning at L88:C11. Expected `<ul>` to be at an indentation of 20 but was found at 10.         indent-style
 89:13  error    Incorrect indentation for `li` beginning at L89:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 90:13  error    Incorrect indentation for `li` beginning at L90:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 91:13  error    Incorrect indentation for `li` beginning at L91:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 92:13  error    Incorrect indentation for `li` beginning at L92:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 95:11  error    Incorrect indentation for `h4` beginning at L95:C11. Expected `<h4>` to be at an indentation of 20 but was found at 10.         indent-style
 96:11  error    Incorrect indentation for `ul` beginning at L96:C11. Expected `<ul>` to be at an indentation of 20 but was found at 10.         indent-style
 97:13  error    Incorrect indentation for `li` beginning at L97:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 98:13  error    Incorrect indentation for `li` beginning at L98:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
 99:13  error    Incorrect indentation for `li` beginning at L99:C13. Expected `<li>` to be at an indentation of 24 but was found at 12.         indent-style
100:13  error    Incorrect indentation for `li` beginning at L100:C13. Expected `<li>` to be at an indentation of 24but was found at 12.        indent-style
106:5   error    Incorrect indentation for `div` beginning at L106:C5. Expected `<div>` to be at an indentation of 8but was found at 4.         indent-style
107:7   error    Incorrect indentation for `ul` beginning at L107:C7. Expected `<ul>` to be at an indentation of 12but was found at 6.          indent-style
108:9   error    Incorrect indentation for `li` beginning at L108:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
109:9   error    Incorrect indentation for `li` beginning at L109:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
109:41  error    The value "show-for-sr" of attribute "class" does not respect the format:underscore                                            id-class-style
110:9   error    Incorrect indentation for `li` beginning at L110:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
111:9   error    Incorrect indentation for `li` beginning at L111:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
112:9   error    Incorrect indentation for `li` beginning at L112:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
113:9   error    Incorrect indentation for `li` beginning at L113:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
114:9   error    Incorrect indentation for `li` beginning at L114:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
115:9   error    Incorrect indentation for `li` beginning at L115:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
116:9   error    Incorrect indentation for `li` beginning at L116:C9. Expected `<li>` to be at an indentation of 16but was found at 8.          indent-style
120:5   error    Incorrect indentation for `script` beginning at L120:C5. Expected `<script>` to be at an indentation of 8 but was found at 4.   indent-style
121:5   error    Incorrect indentation for `script` beginning at L121:C5. Expected `<script>` to be at an indentation of 8 but was found at 4.   indent-style
122:5   error    Incorrect indentation for `script` beginning at L122:C5. Expected `<script>` to be at an indentation of 8 but was found at 4.   indent-style

$ eslint foundation-sites-templates/index.html
...redacted for brevity...

Second, I fed the HTML linters some common & "problematic" HTML mistakes, to see if they were able to detect them.

The HTML file tested can be found there: horribly-broken-page.html. It is served over HTTP there: horribly-broken-page.html.

The table below lists, for each issue of this HTML test file, if each linter is able to detect it:

HTML linters: W3C v.Nu checker html-tidy htmlhint html-validate LintHTML html-eslint
No DOCTYPE
No lang attribute
No <title>
No explicit <body>
<link> with missing required attributes
Multiple <main> blocks
Unknown HTML tag
Tag in UPPERCASE
Stray end tag
Empty ID attribute
Duplicate IDs
Duplicate attribute
Deprecated attribute
Empty <figure> tag in body
figcaption without figure parent
Invalid item within list
Improperly nested div, inside p
dt/dd without correct parent

Of course this is not exhaustive: some linters include hundreds of checks! There is some "select bias" due to the fact that I selected some specific HTML issues.

Analysis details:

horribly-broken-page.html
$ vnu --Werror content/test-html-linters/run-all-html-linters.sh
1.34-2.7: error: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
1.34-2.7: error: Element “link” is missing one or more of the following attributes: “href”, “itemprop”, “property”, “rel”, “resource”.
1.34-2.7: error: A “link” element must have an “href” or “imagesrcset” attribute, or both.
5.1-5.6: error: Element “head” is missing a required instance of child element “title”.
6.1-6.6: error: A document must not include more than one visible “main” element.
9.1-9.7: error: Element “dummy” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)
15.1-15.10: error: Stray end tag “section”.
18.1-18.11: error: Bad value “” for attribute “id” on element “div”: An ID must not be the empty string.
22.1-22.15: error: Duplicate ID “MyID”.
21.1-21.15: info warning: The first occurrence of ID “MyID” was here.
25.1-25.17: error: Duplicate attribute “foo”.
25.1-25.23: error: Attribute “foo” not allowed on element “p” at this point.
27.1-27.17: error: Attribute “bgcolor” not allowed on element “p” at this point.
32.1-32.12: error: Element “figcaption” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)
37.3-37.5: error: Element “p” not allowed as child of element “ul” in this context. (Suppressing further errors from this subtree.)
47.1-47.4: error: No “p” element in scope but a “p” end tag seen.
49.1-49.4: error: Element “dt” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)
53.1-53.4: error: Element “dd” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)
1.34-2.7: info warning: Consider adding a “lang” attribute to the “html” start tag to declare the language of this document.

$ tidy -errors content/test-html-linters/horribly-broken-page.html
line 2 column 1 - Warning: missing <!DOCTYPE> declaration
line 5 column 1 - Warning: inserting implicit <body>
line 6 column 1 - Error: discarding unexpected <main>
line 6 column 37 - Error: discarding unexpected </main>
line 9 column 1 - Error: <dummy> is not recognized!
line 9 column 1 - Error: discarding unexpected <dummy>
line 9 column 8 - Error: discarding unexpected </dummy>

$ htmlhint content/test-html-linters/horribly-broken-page.html
L1 |<!-- Empty `link` tag in body -->
    ^ Doctype must be declared first. (doctype-first)
L12 |<SPAN>Tag in UPPERCASE</SPAN>
     ^ The html element name of [ SPAN ] must be in lowercase. (tagname-lowercase)
L12 |<SPAN>Tag in UPPERCASE</SPAN>
                           ^ The html element name of [ SPAN ] must be in lowercase. (tagname-lowercase)
L22 |<div id="MyID"><code>div</code> with ID <code>MyId</code></div>
         ^ The id value [ MyID ] must be unique. (id-unique)
L25 |<p foo="bar" foo="baz">This paragraph has a duplicate unknown attribute</p>
                 ^ Duplicate of attribute name [ foo ] was found. (attr-no-duplication)
L41 |</ul>
     ^ Tag must be paired, missing: [ </li> ], start tag match failed [ <li> ] on line 40. (tag-pair)

$ html-validate content/test-html-linters/horribly-broken-page.html
 2:2   error  <link> is missing required "href" attribute                                                 element-required-attributes
 5:2   error  Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)  unique-landmark
 6:2   error  Multiple <main> elements present in document                                                no-multiple-main
 6:2   error  Landmarks must have a non-empty and unique accessible name (aria-label or aria-labelledby)  unique-landmark
 9:2   error  <dummy> is not a valid element name                                                         element-name
12:2   error  Element "SPAN" should be lowercase                                                          element-case
15:2   error  Stray end tag '</section>'                                                                  close-order
18:6   error  element id "" must not be empty                                                             valid-id
18:6   error  Attribute "id" has invalid value ""                                                         attribute-allowed-values
22:10  error  Duplicate ID "MyID"                                                                         no-dup-id
25:14  error  Attribute "foo" duplicated                                                                  no-dup-attr
37:4   error  <p> element is not permitted as content under <ul>                                          element-permitted-content
40:4   error  Element <li> is implicitly closed by parent </ul>                                           no-implicit-close
43:2   error  Element <p> is implicitly closed by adjacent <div>                                          no-implicit-close
47:2   error  Stray end tag '</p>'                                                                        close-order
49:2   error  <dt> element requires a "dl > dt" or "dl > div > dt" ancestor                               element-required-ancestor
53:2   error  <dd> element requires a "dl > dd" or "dl > div > dd" ancestor                               element-required-ancestor

$ linthtml content/test-html-linters/horribly-broken-page.html
12:1  error    Invalid case for tag <span>, tag names must be written in lowercase                                                       tag-name-lowercase
18:6  error    The attribute "id" requires a value                                                                                       attr-req-value
21:6  error    The value "MyID" of attribute "id" does not respect the format:underscore                                                id-class-style
22:9  error    The id "MyID" is already used atL21:c9                                                                                   id-no-dup
22:6  error    The value "MyID" of attribute "id" does not respect the format:underscore                                                id-class-style
25:4  error    The attribute foo is duplicated                                                                                           attr-no-dup
27:4  error    The attribute "bgcolor" attribute is cannot be used as it's banned                                                        attr-bans
37:3  error    Incorrect indentation for `p` beginning at L37:C3. Expected `<p>` to be at an indentation of 4 but was found at 2.        indent-style
40:3  error    Incorrect indentation for `li` beginning at L40:C3. Expected `<li>` to be at an indentation of 4 but was found at 2.      indent-style
40:3  error    Tag is not closed                                                                                                        tag-close
44:3  error    Incorrect indentation for `p` beginning at L44:C3. Expected `<\p>` to be at an indentation of 0 but was found at 2.       indent-style
43:1  error    Tag is not closed                                                                                                        tag-close
47:1  error    Tag close does not match opened tag atC47:L1                                                                             tag-name-match
50:3  error    Incorrect indentation for `code` beginning at L50:C3. Expected `<code>` to be at an indentation of 4but was found at 2.  indent-style
54:3  error    Incorrect indentation for `code` beginning at L54:C3. Expected `<code>` to be at an indentation of 4 but was found at 2.  indent-style

$ eslint content/test-html-linters/horribly-broken-page.html
21:10  error  The id 'MyID' is duplicated                          @html-eslint/no-duplicate-id
22:10  error  The id 'MyID' is duplicated                          @html-eslint/no-duplicate-id
25:14  error  The attribute 'foo' is duplicated                    @html-eslint/no-duplicate-attrs
33:1   error  Expected indentation of 4 space but found 2 space    @html-eslint/indent
36:1   error  Missing closing tag for ul                           @html-eslint/require-closing-tags
37:1   error  Expected indentation of 4 space but found 2 space    @html-eslint/indent
38:1   error  Expected indentation of 8 space but found 4 space    @html-eslint/indent
39:1   error  Expected indentation of 4 space but found 2 space    @html-eslint/indent
40:1   error  Expected indentation of 4 space but found 2 space    @html-eslint/indent
40:3   error  Missing closing tag for li                           @html-eslint/require-closing-tags
43:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent
44:1   error  Expected indentation of 12 space but found 2 space   @html-eslint/indent
45:1   error  Expected indentation of 16 space but found 4 space   @html-eslint/indent
46:1   error  Expected indentation of 12 space but found 2 space   @html-eslint/indent
47:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent
49:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent
50:1   error  Expected indentation of 12 space but found 2 space   @html-eslint/indent
51:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent
53:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent
54:1   error  Expected indentation of 12 space but found 2 space   @html-eslint/indent
55:1   error  Expected indentation of 8 space but found no indent  @html-eslint/indent

Conclusion

Overall, I would recommend using the W3C v.Nu checker: apart from its (relative) slowness, it's actively maintained, easy to setup in a CI pipeline and to whitelist some issues, while still getting relevant warnings with a wide range of checks.

However, note that "HTML linting" is very wide, and can cover all of the following goals:

  • homogenize HTML code formatting
  • detect HTML syntax critical issues, that could "break" content rendering
  • detect security issues in HTML code (e.g. related to <script> or <iframe> usages)
  • detect accessibility issues in HTML code
  • recommend "best practices", about HTML5 / document structure / accessibility / etc.

In the spirit of the "Do one thing and do it well" Linux principle, maybe the best approach should be to have "specialized" HTML linters that only address some of those specific areas, and combine them?

Related tools & readings