Every code HtmlValidator reports, its message, and what to change so the content passes. The
message is the template with the detail filled in at %s. Codes never change once
released; message wording can, so match on code.
| Code | Message | What to do |
|---|---|---|
not-utf8 |
Content must be UTF-8, this is not: %s |
Convert the text to UTF-8 before the check. A Latin-1 é from an old export is the usual cause: mb_convert_encoding($html, 'UTF-8', 'Windows-1252') when the source encoding is known. Text typed into a form is already UTF-8. |
control-character |
Content contains a control character: %s |
Remove the character; the detail names its byte offset. A NUL comes from a cut-off binary paste or a bad conversion, an escape (\033) from terminal output pasted in. Tab, LF and CR are fine. |
element-not-allowed |
%s is not allowed |
Remove the tag. <script>: put the script in the page template. <xml>, <html>, <head>: a raw Word paste, so paste as text or use the editor's paste-from-Word. <?import ...>: bound a behavior in old IE; remove it. A tag inside <!--[if ...]>: the same fix as outside it. <form>: set HtmlValidator::$allowForms. <style>: $allowStyles. <iframe>: $allowEmbeds. <svg>: upload the file and use <img>. A custom element (<my-widget>): use a <div> or <span> with a class. <!DOCTYPE>, <html>, <head>, <body>, <meta>, <title>: paste only the body content. |
event-handler |
%s= event handler attributes are not allowed |
Remove the attribute. Put the behavior in the page's own script and select the element by a class or id. |
attribute-not-allowed |
The %s attribute is not allowed |
Remove the attribute. data-* or another unknown name: use a class instead. srcdoc: use src with a listed host instead. xmlns:*: remove it; only Microsoft Office namespaces pass. formaction: set HtmlValidator::$allowForms. style: set $allowStyles, or move the styles to the site's stylesheet. |
url-scheme-not-allowed |
The URL in %s must start with http:, https:, mailto:, tel: or another allowed scheme |
Use an http:, https:, mailto: or tel: URL, or a relative path. A data: URL passes only as image/... on <img src>; anywhere else, upload the file and link it. A javascript: link: use href="#" and a page script. Another scheme your visitors' machines should open (sms:, an app link): add it to HtmlValidator::$urlSchemes. The value is quoted as a browser decodes it, so : shows as :. |
iframe-host |
Embedding frames from %s is not allowed |
Add the host to HtmlValidator::$iframeHosts, spelled as it appears in the embed code (player.vimeo.com, not vimeo.com), with no scheme, port or path. An <iframe> with no src has nothing to allow: remove it. |
css-not-allowed |
CSS containing %s is not allowed |
Remove the construct. expression(, behavior:, -moz-binding: old IE and Firefox only, nothing renders them now. @import: link the stylesheet from the page template. [value^=, [href$=, [name*=: use an exact match ([value=) or a class. unicode-range: drop it from @font-face. A backslash outside a comment or quoted string, or inside a quoted url(): write the character itself. url(data:...): upload the image and use its path. If the detail is the start of the block rather than a token, the block is past the regex engine's limit (megabytes of `x* |
less-than-in-text |
A < where a browser reads text, not tags: %s |
The < is inside <style>, <iframe>, <textarea> or a comment that is not <!-- --> (<?php ... ?>, <!x ...>), where a browser reads it as text but strip_tags() with an allow list or an older DOM library reads it as a tag. In a <textarea> write <. In <style> remove it; CSS has no use for one outside a string. In an <iframe> remove the fallback content. A <?...> or <!...> comment: write it as <!-- ... --> or remove it. The detail starts at the < and runs to the end of the text. |
unclosed-markup |
%s is not closed |
Close it: add the missing >, closing quote, --> or </style>. The detail starts where the open piece began, so the fix is at its end. A cut-off paste is the usual cause. |
To translate a message, run template through your translation function and put detail
back with sprintf(), then encode the whole result, since the detail holds <, > and
quotes: echo htmlspecialchars(sprintf(t($violation->template), $violation->detail));.
Violation::TEMPLATES holds every template keyed by code, so a translation system can
register them all up front.
Three things to know when reading a result:
- A byte error arrives alone.
not-utf8andcontrol-characterstop the check before the content is read as HTML. Fix the bytes and check again; the other problems are reported then. - A refused tag reports once.
<script onload="x" src="javascript:1">is oneelement-not-allowed, not three errors. Its attributes are not checked, since the tag is going away. - The same problem on five tags is reported once. Errors are deduplicated by code and
detail: five
<script>tags are one error, five differenton*attributes are five. The list stops atHtmlValidator::$maxErrorsdistinct problems (50 by default), andunclosed-markupis always last.