HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The menuitem role represents an option in a set of choices and is only meaningful when it exists within the context of a menu. When a menuitem appears outside of a menu or menubar, screen readers and other assistive technologies have no way to determine that it belongs to a menu widget. They cannot announce the total number of items, provide keyboard navigation between items, or convey the menu's hierarchical structure to the user.
This requirement follows the concept of required owned elements and required context roles in ARIA. Just as a <li> element belongs inside a <ul> or <ol>, a menuitem belongs inside a menu or menubar. The relationship can be established in two ways:
- DOM nesting — the
menuitemelement is a DOM descendant of themenuormenubarelement. - The
aria-ownsattribute — themenuormenubarelement usesaria-ownsto reference themenuitemby itsid, establishing ownership even when the elements aren't nested in the DOM.
It's important to note that ARIA menu roles are intended for application-style menus — the kind you'd find in a desktop application (e.g., File, Edit, View menus). They are not meant for standard website navigation. For typical site navigation, use semantic HTML elements like <nav> with <ul>, <li>, and <a> elements instead.
How to fix it
- Identify every element with
role="menuitem"in your markup. - Ensure each one is contained within an element that has
role="menu"orrole="menubar", either through DOM nesting or viaaria-owns. - Choose the correct parent role:
- Use
role="menubar"for a persistent, typically horizontal menu bar (like a desktop application's top-level menu). - Use
role="menu"for a popup or dropdown menu that contains a group of menu items.
- Use
- If you're using menus for site navigation, consider removing the ARIA menu roles entirely and using semantic HTML (
<nav>,<ul>,<li>,<a>) instead.
Examples
Incorrect — menuitem without a menu context
This triggers the validator error because the menuitem elements have no parent menu or menubar:
<div>
<div role="menuitem">Cut</div>
<div role="menuitem">Copy</div>
<div role="menuitem">Paste</div>
</div>
Correct — menuitem inside a menu
Wrapping the items in an element with role="menu" resolves the issue:
<div role="menu">
<div role="menuitem" tabindex="0">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
Correct — menuitem inside a menubar
For a persistent horizontal menu bar with application-style actions:
<div role="menubar">
<div role="menuitem" tabindex="0">File</div>
<div role="menuitem" tabindex="-1">Edit</div>
<div role="menuitem" tabindex="-1">View</div>
</div>
Correct — nested menus with dropdown submenus
A menubar with a dropdown menu containing additional menuitem elements:
<div role="menubar">
<div role="menuitem" tabindex="0" aria-haspopup="true" aria-expanded="false">
File
<div role="menu">
<div role="menuitem" tabindex="-1">New</div>
<div role="menuitem" tabindex="-1">Open</div>
<div role="menuitem" tabindex="-1">Save</div>
</div>
</div>
<div role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false">
Edit
<div role="menu">
<div role="menuitem" tabindex="-1">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
</div>
</div>
Correct — using aria-owns for ownership without DOM nesting
When the menuitem elements cannot be nested inside the menu in the DOM (e.g., due to layout constraints), use aria-owns to establish the relationship:
<div role="menu" aria-owns="item-cut item-copy item-paste"></div>
<div role="menuitem" id="item-cut" tabindex="0">Cut</div>
<div role="menuitem" id="item-copy" tabindex="-1">Copy</div>
<div role="menuitem" id="item-paste" tabindex="-1">Paste</div>
Better alternative — use semantic HTML for site navigation
If you're building standard website navigation (not an application-style menu), avoid ARIA menu roles altogether:
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
This approach is simpler, more accessible by default, and doesn't trigger the validator warning. Reserve role="menu", role="menubar", and role="menuitem" for true application-style menus that implement full keyboard interaction patterns as described in the ARIA Authoring Practices Guide.
The aria-describedby attribute cannot be an empty string. It must contain at least one valid ID reference, or be removed entirely.
The aria-describedby attribute accepts a space-separated list of id values that point to elements providing a description for the current element. When a screen reader encounters an element with aria-describedby, it reads the text content of each referenced element as additional context.
An empty string ("") is not a valid IDREF value. The W3C validator expects at least one non-whitespace character. This often happens when a template engine or JavaScript outputs an empty variable into the attribute, or when a CMS generates the attribute without a corresponding value.
If no description exists, remove the attribute altogether. An absent aria-describedby is perfectly fine — it simply means the element has no supplementary description. Assistive technologies handle missing attributes gracefully, but an empty one can cause unpredictable behavior.
Examples
Invalid: empty aria-describedby
<a href="/settings" aria-describedby="">Account settings</a>
Fixed: attribute removed
<a href="/settings">Account settings</a>
Fixed: attribute references a valid ID
<a href="/settings" aria-describedby="settings-note">Account settings</a>
<p id="settings-note">Opens your profile and notification preferences.</p>
The <label> element associates descriptive text with a specific form control, enabling users to click the label to focus or activate the associated input. The for attribute creates this link by referencing the id of the target form control. When the referenced id doesn't correspond to a valid, non-hidden form control, the label becomes orphaned — it isn't associated with anything meaningful.
The W3C validator raises this error in several scenarios:
- The
forattribute references anidthat doesn't exist in the document. - The
forattribute references an element that isn't a labelable element (such as a<div>or<span>). - The
forattribute references an<input type="hidden">, which is not a visible form control and cannot be labeled. - There's a typo or mismatch between the
forvalue and the intended element'sid.
Labelable elements in HTML include <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
This matters for accessibility because screen readers rely on the for/id association to announce what each form control represents. Without a valid association, users who depend on assistive technology may not understand what a form field is asking for. It also impacts usability — a properly linked label expands the clickable area for the form control, making it easier to interact with, especially on touch devices and for users with motor impairments.
To fix this issue, verify that the for attribute value exactly matches the id of a visible, labelable form control. If you're labeling a hidden input, consider whether the label is necessary at all (hidden inputs are not user-facing). If the target element isn't a form control, either change it to the appropriate form element or use a different approach like aria-labelledby.
Examples
❌ for references a non-existent ID
<label for="username">Username</label>
<input id="user-name" type="text">
The for value "username" doesn't match the input's id of "user-name".
✅ Fixed: matching for and id
<label for="username">Username</label>
<input id="username" type="text">
❌ for references a hidden input
<label for="token">Token</label>
<input id="token" type="hidden" value="abc123">
An <input type="hidden"> is not a visible form control and cannot be labeled.
✅ Fixed: remove the unnecessary label
<input id="token" type="hidden" value="abc123">
Hidden inputs don't need labels since users never interact with them directly.
❌ for references a non-labelable element
<label for="info">Information</label>
<div id="info">Some details here</div>
A <div> is not a labelable element, so the for association is invalid.
✅ Fixed: use a proper form control or implicit labeling
<label for="info">Information</label>
<textarea id="info"></textarea>
❌ for references an element inside another form control
<label for="opt">Choose one</label>
<select>
<option id="opt" value="a">Option A</option>
</select>
An <option> element is not a labelable element. The label should point to the <select>.
✅ Fixed: reference the <select> element
<label for="choice">Choose one</label>
<select id="choice">
<option value="a">Option A</option>
</select>
Using implicit labeling as an alternative
Instead of using the for attribute, you can wrap the form control inside the <label> element. This creates an implicit association without needing for or id at all:
<label>
Age
<input type="number">
</label>
This approach avoids the for/id mismatch problem entirely and is equally valid for accessibility.
The aria-describedby attribute cannot be an empty string — it must either contain valid ID references or be removed entirely.
The aria-describedby attribute accepts one or more ID values (separated by spaces) that point to elements providing additional descriptive text for the current element. When a screen reader focuses on the element, it reads the content of the referenced elements to give the user more context.
Setting aria-describedby="" is invalid because the attribute expects at least one valid IDREF — a non-empty string that matches the id of another element in the page. An empty value doesn't reference anything and creates a validation error. If no description is needed, simply omit the attribute altogether.
This commonly happens when a template or JavaScript dynamically sets the attribute but provides an empty fallback value instead of removing the attribute entirely.
Invalid Example
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="">
Fixed Examples
If there is no description to reference, remove the attribute:
<label for="email">Email</label>
<input type="email" id="email">
If a description exists, point to its id:
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-hint">
<p id="email-hint">We'll never share your email with anyone.</p>
If you're generating the attribute dynamically, make sure your code removes aria-describedby entirely rather than setting it to an empty string when no hint is available.
The aria-controls attribute must contain at least one valid ID reference and cannot be an empty string.
The aria-controls attribute accepts a space-separated list of IDs pointing to elements that are controlled by the current element. When a screen reader encounters aria-controls, it can offer navigation to those referenced elements. An empty string is not a valid IDREF value per the WAI-ARIA specification, so the W3C validator rejects it.
If the element does not currently control anything, remove the aria-controls attribute entirely rather than leaving it empty. Setting it to an empty string does not mean "no value" — it means "invalid value," which can confuse assistive technologies.
If the controlled element is added dynamically (for example, a dropdown that appears after user interaction), add aria-controls through JavaScript at the same time the controlled element appears in the DOM, and remove it when the element is gone.
Examples
Invalid: empty aria-controls
<div aria-controls="" aria-expanded="false">
Toggle panel
</div>
Fixed: remove the attribute when unused
<div aria-expanded="false">
Toggle panel
</div>
Fixed: provide a valid ID when a controlled element exists
<div aria-controls="panel-1" aria-expanded="true">
Toggle panel
</div>
<div id="panel-1">
Panel content here.
</div>
The srcset attribute lets you provide multiple image sources so the browser can select the most appropriate one. There are two types of descriptors you can use in srcset: pixel density descriptors (e.g., 1x, 2x) and width descriptors (e.g., 400w, 800w). When you use pixel density descriptors, the browser already knows the relationship between each source — it simply picks the one matching the device's pixel ratio. But width descriptors work differently. They tell the browser the intrinsic pixel width of each image file, and the browser then needs to know how wide the image will actually be rendered on screen to calculate which file is the best fit. That's exactly what the sizes attribute provides.
The sizes attribute accepts a comma-separated list of media conditions paired with length values, plus a default length. For example, sizes="(max-width: 600px) 100vw, 50vw" tells the browser: "If the viewport is 600px wide or less, this image will occupy 100% of the viewport width; otherwise, it will occupy 50%." Armed with this information and the width descriptors in srcset, the browser can do the math and download only the most suitable image — before CSS or layout has even been calculated.
Why this matters
- Standards compliance: The HTML specification requires
sizeswheneversrcsetuses width descriptors. Omitting it produces invalid HTML. - Correct image selection: Without
sizes, browsers fall back to assuming the image will be100vwwide, which often leads to downloading unnecessarily large images on desktop layouts where the image is much smaller than the full viewport. - Performance: Serving oversized images wastes bandwidth and slows page load. A proper
sizesattribute ensures the browser downloads the smallest sufficient image. - Predictable behavior: Relying on the browser's fallback assumption (
100vw) makes your responsive images behave inconsistently and defeats the purpose of providing multiple candidates.
How to fix it
- Identify every
img(orsource) element that uses width descriptors insrcset. - Determine how wide the image will be displayed at different viewport sizes. You can inspect this with your browser's developer tools or by reviewing your CSS.
- Add a
sizesattribute that describes those widths using media conditions and CSS length values likepx,vw,em, orcalc()expressions.
Examples
Incorrect — missing sizes with width descriptors
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
alt="A mountain landscape">
This triggers the validation error because the browser sees width descriptors (400w, 800w, 1200w) but has no sizes attribute to determine the image's rendered width.
Correct — sizes attribute added
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 600px"
alt="A mountain landscape">
Here, the sizes attribute tells the browser:
- On viewports up to 600px wide, the image fills 100% of the viewport.
- On viewports between 601px and 1000px, the image fills 50% of the viewport.
- On larger viewports, the image is displayed at a fixed 600px width.
Correct — pixel density descriptors (no sizes needed)
<img
src="logo-1x.png"
srcset="logo-1x.png 1x, logo-2x.png 2x"
alt="Company logo">
When using pixel density descriptors (1x, 2x) instead of width descriptors, the sizes attribute is not required. The browser simply matches the descriptor to the device's pixel ratio.
Correct — using sizes with a <picture> element
<picture>
<source
srcset="hero-400.webp 400w, hero-800.webp 800w"
sizes="(max-width: 800px) 100vw, 800px"
type="image/webp">
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Hero banner">
</picture>
The sizes attribute is required on both the source and img elements when either uses width descriptors in its srcset.
The HTML specification mandates that documents must be encoded in UTF-8. This requirement exists because UTF-8 is the universal character encoding that supports virtually every character from every writing system in the world. Older encodings like windows-1252, iso-8859-1, or shift_jis only support a limited subset of characters and can cause text to display incorrectly — showing garbled characters or question marks — especially for users in different locales or when content includes special symbols, accented letters, or emoji.
When the validator encounters charset=windows-1252 in your <meta> tag, it flags this as an error because the HTML living standard (WHATWG) explicitly states that the character encoding declaration must specify utf-8 as the encoding. This isn't just a stylistic preference — browsers and other tools rely on this declaration to correctly interpret the bytes in your document. Using a non-UTF-8 encoding can lead to security vulnerabilities (such as encoding-based XSS attacks) and accessibility issues when assistive technologies misinterpret characters.
To fix this issue, take two steps:
- Update the
<meta>tag to declareutf-8as the charset. - Re-save your file with UTF-8 encoding. Most modern code editors (VS Code, Sublime Text, etc.) let you choose the encoding when saving — look for an option like "Save with Encoding" or check the status bar for the current encoding. If your file was originally in
windows-1252, simply changing the<meta>tag without re-encoding the file could cause existing special characters to display incorrectly.
The HTML spec also recommends using the shorter <meta charset="utf-8"> form rather than the longer <meta http-equiv="Content-Type" ...> pragma directive, as it's simpler and achieves the same result. Either form is valid, but the charset declaration must appear within the first 1024 bytes of the document.
Examples
Incorrect: Using windows-1252 charset
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
This triggers the validator error because the charset is not utf-8.
Correct: Using the short charset declaration (recommended)
<meta charset="utf-8">
Correct: Using the http-equiv pragma directive with utf-8
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Note that the <meta charset="utf-8"> tag should be the first element inside <head>, before any other elements (including <title>), so the browser knows the encoding before it starts parsing the rest of the document.
The label element associates a caption with a form control. There are two ways to create this association:
- Implicit association — Place the form control directly inside the
labelelement. Nofororidattributes are needed. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the associated form control.
Both methods work independently. The problem arises when you mix them incorrectly — nesting a select inside a label that has a for attribute, but the select either has no id or has an id that doesn't match the for value. In this situation, the explicit association (via for) points to nothing or to the wrong element, while the implicit association (via nesting) still exists. This contradictory state violates the HTML specification and can cause assistive technologies like screen readers to misidentify or skip the label, reducing accessibility for users who rely on them.
The WHATWG HTML specification requires that when a for attribute is present on a label, it must reference a valid labelable element by id. If the form control is already nested inside the label, the for attribute is redundant — but if present, it still must correctly reference that control.
How to Fix
You have two options:
- Remove the
forattribute — If theselectis already inside thelabel, implicit association handles everything. This is the simplest fix. - Add or correct the
id— Keep theforattribute but ensure theselecthas a matchingid.
Examples
❌ Incorrect: for attribute with no matching id
The for attribute references "age", but the select has no id at all:
<label for="age">
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: for attribute with a mismatched id
The for attribute references "age", but the select has a different id:
<label for="age">
Age
<select id="age-select">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 1: Remove the for attribute (implicit association)
Since the select is nested inside the label, implicit association is sufficient:
<label>
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 2: Match the for attribute with the id (explicit association)
Keep the for attribute and give the select a matching id:
<label for="age">
Age
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 3: Separate the label and select (explicit association only)
If you prefer to keep the select outside the label, explicit association with matching for and id is required:
<label for="age">Age</label>
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
In most cases, option 1 (removing the for attribute) is the cleanest solution when the control is already nested. Use explicit association when the label and control are in separate parts of the DOM, such as in complex table or grid layouts.
The meta element's charset declaration must specify utf-8 when using HTML5. Values like iso-8859-15 are not valid in the HTML living standard.
In HTML5, the only permitted character encoding declared via a meta element is UTF-8. This applies to both the shorthand form (<meta charset="utf-8">) and the longer http-equiv form (<meta http-equiv="Content-Type" content="text/html; charset=utf-8">). Older encodings like iso-8859-15, windows-1252, or iso-8859-1 are not allowed.
The WHATWG HTML standard requires documents to be encoded in UTF-8. If a document uses a legacy encoding, the validator rejects it because the specification explicitly states that the charset value must be utf-8 (case-insensitive).
The shorthand <meta charset="utf-8"> is the preferred form in HTML5. It is shorter and does exactly the same thing as the http-equiv variant. There is no need to use both.
When switching from a legacy encoding, make sure the file itself is actually saved as UTF-8. Declaring UTF-8 in the markup while the file is saved in a different encoding will cause garbled characters. Most modern text editors let you choose the encoding when saving.
Examples
Invalid
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
Valid
<meta charset="utf-8">
The as attribute specifies the type of content a <link> element is fetching — such as "style", "script", "font", or "image". The browser uses this information to set the correct request headers, apply the right Content Security Policy, and assign the appropriate priority to the request. However, the HTML specification restricts the as attribute to <link> elements whose rel attribute is either "preload" or "modulepreload". Using as with any other rel value (like "stylesheet", "icon", or a missing rel altogether) is invalid HTML.
This validation error commonly occurs in two scenarios:
- You intended to preload a resource but forgot to set
rel="preload"— for example, writing<link href="styles.css" as="style">without specifyingrel. - You added
asto a regular<link>by mistake — for example, writing<link rel="stylesheet" href="styles.css" as="style">, whereasis unnecessary becauserel="stylesheet"already tells the browser what type of resource it is.
Getting this right matters for several reasons. Browsers rely on valid rel values to determine how to handle linked resources. An incorrect combination may cause the browser to ignore the as attribute entirely, leading to double-fetching of resources or incorrect prioritization. Additionally, invalid HTML can cause unpredictable behavior across different browsers.
How to fix it
- If you want to preload a resource (font, stylesheet, image, script, etc.), set
rel="preload"and keep theasattribute. - If you want to preload a JavaScript module, set
rel="modulepreload". Theasattribute defaults to"script"for module preloads and is optional in that case. - If you're using a different
relvalue like"stylesheet"or"icon", remove theasattribute — it's not needed and not allowed.
Examples
Incorrect: as attribute without rel="preload"
This <link> has as="style" but no rel attribute:
<link href="styles.css" as="style">
Incorrect: as attribute with rel="stylesheet"
The as attribute is not valid on a stylesheet link:
<link rel="stylesheet" href="styles.css" as="style">
Correct: preloading a stylesheet
Use rel="preload" with the as attribute to hint the resource type:
<link rel="preload" href="styles.css" as="style">
Note that preloading a stylesheet doesn't apply it — you still need a separate <link rel="stylesheet"> to actually use the CSS.
Correct: preloading a font
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
The crossorigin attribute is required when preloading fonts, even if they're served from the same origin.
Correct: preloading a JavaScript module
<link rel="modulepreload" href="app.js">
With rel="modulepreload", the as attribute defaults to "script", so you can omit it. You may still include it explicitly if you prefer:
<link rel="modulepreload" href="app.js" as="script">
Correct: regular stylesheet (no as needed)
If you simply want to load a stylesheet, no as attribute is required:
<link rel="stylesheet" href="styles.css">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Preload Example</title>
<!-- Preload critical resources -->
<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="hero.jpg" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Apply the stylesheet -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
<img src="hero.jpg" alt="Hero banner">
</body>
</html>
The label element represents a caption for a form control. There are two ways to associate a label with its control:
- Implicit association — Place the form control directly inside the
labelelement. Noforattribute is needed because the browser automatically pairs the label with the nested control. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the target form control. The control doesn't need to be nested inside thelabelin this case.
Both methods are valid on their own. The problem occurs when you combine them incorrectly: you nest an input inside a label that has a for attribute, but the input either has no id or has an id that doesn't match the for value. This creates a contradiction — the for attribute points to a specific id, yet the nested input doesn't fulfill that reference. Browsers may handle this inconsistently, and assistive technologies like screen readers could fail to announce the label correctly, harming accessibility.
Why this matters
- Accessibility: Screen readers rely on the
for/idpairing to announce labels for form controls. A mismatched or missingidcan leave the control unlabeled for users who depend on assistive technology. - Standards compliance: The HTML specification requires that when a
forattribute is present, it must reference theidof a labelable element. A mismatch violates this rule. - Browser behavior: Some browsers will fall back to the implicit association when
fordoesn't resolve, but others may prioritize the broken explicit association, leaving the control effectively unlabeled.
How to fix it
You have two options:
- Remove the
forattribute if theinputis already nested inside thelabel. The implicit association is sufficient on its own. - Add or correct the
idon the nestedinputso it matches theforattribute value exactly.
Examples
❌ Nested input with no matching id
The for attribute says "email", but the input has no id at all:
<label for="email">
<input type="email" name="email">
</label>
❌ Nested input with a mismatched id
The for attribute says "email", but the input's id is "user-email":
<label for="email">
<input type="email" name="email" id="user-email">
</label>
✅ Fix by removing the for attribute (implicit association)
Since the input is nested inside the label, the association is automatic:
<label>
<input type="email" name="email">
</label>
✅ Fix by adding a matching id (explicit association)
The for value and the id value are identical:
<label for="email">
<input type="email" name="email" id="email">
</label>
✅ Fix by using explicit association without nesting
If you prefer to keep the for attribute, the input doesn't need to be nested at all:
<label for="email">Email</label>
<input type="email" name="email" id="email">
In most cases, choosing either implicit or explicit association — rather than mixing both — is the simplest way to avoid this error. If you do combine them, just make sure the for value and the id value match exactly.
The core problem is that aria-disabled="true" is purely an accessibility hint — it communicates a disabled state to assistive technologies like screen readers, but it has no effect on the actual behavior of the element. When an a element has an href attribute, the browser treats it as a valid hyperlink regardless of any ARIA attributes. Users can still click it, follow it via keyboard navigation, and navigate to its destination. This mismatch between the announced state ("disabled") and actual behavior ("fully functional link") creates a confusing and misleading experience, particularly for users of assistive technologies.
The W3C validator flags this combination because it violates the principle that ARIA states should accurately reflect an element's true interactive state. A link that claims to be disabled but still works undermines user trust and can cause real usability problems.
Why this matters
- Accessibility: Screen readers will announce the link as disabled, but users who activate it will be unexpectedly navigated away. This is disorienting and violates WCAG guidance on predictable behavior.
- Standards compliance: The HTML specification and ARIA in HTML requirements discourage or disallow this combination because it produces an unreliable user experience.
- Browser behavior: No browser will disable a link just because
aria-disabled="true"is present. Thehrefattribute always makes theaelement an active hyperlink.
How to fix it
You have two main approaches depending on your intent:
The link should be active: Remove
aria-disabled="true"and keep thehref. If the link works, don't mark it as disabled.The link should be disabled: Remove the
hrefattribute. Withouthref, theaelement becomes a placeholder link that is not interactive. You can then usearia-disabled="true"to communicate the disabled state,tabindex="-1"to remove it from the keyboard tab order, and CSS to style it as visually disabled. You should also add JavaScript to prevent activation if needed.
Examples
Incorrect
This triggers the validation error because aria-disabled="true" conflicts with the presence of href:
<a href="/dashboard" aria-disabled="true">Go to Dashboard</a>
Correct — Keep the link active
If the link should function normally, simply remove the aria-disabled attribute:
<a href="/dashboard">Go to Dashboard</a>
Correct — Disable the link
If the link should be non-actionable (e.g., a navigation item the user doesn't currently have access to), remove the href attribute and use ARIA and CSS to communicate the disabled state:
<a aria-disabled="true" tabindex="-1" role="link" class="link-disabled">Go to Dashboard</a>
.link-disabled {
color: #6c757d;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
}
In this approach:
- Removing
hrefensures the link is not actionable by the browser. aria-disabled="true"tells assistive technologies the element is disabled.tabindex="-1"removes the element from the keyboard tab order so users can't Tab to it.role="link"preserves the link semantics so screen readers still identify it as a link (anawithouthrefloses its implicit link role).- The CSS provides a visual indication that the element is disabled, with
pointer-events: nonepreventing mouse clicks andcursor: not-allowedgiving a visual cue on hover.
Correct — Use a button instead
If the "link" triggers an action rather than navigating somewhere, consider using a button element instead. Buttons natively support the disabled attribute:
<button type="button" disabled>Perform Action</button>
This is the simplest and most robust solution when the element doesn't need to be a link. The disabled attribute is natively understood by browsers and assistive technologies without any ARIA workarounds.
Unicode allows certain characters — especially accented letters and other composed characters — to be represented in multiple ways. For example, the letter "é" can be a single precomposed character (U+00E9, NFC form) or a base letter "e" (U+0065) followed by a combining acute accent (U+0301, NFD form). While they may look identical on screen, they are different byte sequences. The HTML specification requires that all attribute values use NFC to ensure consistent behavior across browsers, search engines, and assistive technologies.
This matters for several important reasons:
- String matching and comparison: Browsers and scripts may compare attribute values byte-by-byte. An
idvalue in NFD form won't match a CSS selector or fragment identifier targeting the NFC form, causing broken links and broken styles. - Accessibility: Screen readers and other assistive technologies may process NFC and NFD strings differently, potentially mispronouncing text or failing to match ARIA references.
- Interoperability: Different operating systems produce different normalization forms by default (macOS file systems historically use NFD, for example). Copying text from various sources can introduce non-NFC characters without any visual indication.
- Standards compliance: The WHATWG HTML specification and W3C guidance on normalization explicitly recommend NFC for all HTML content.
The issue most commonly appears when attribute values contain accented characters (like in id, class, alt, title, or value attributes) that were copied from a source using NFD normalization, or when files are created on systems that default to NFD.
To fix the problem, you need to convert the affected attribute values to NFC. You can do this by:
- Retyping the characters directly in your editor, which usually produces NFC by default.
- Using a programming tool such as Python's
unicodedata.normalize('NFC', text), JavaScript'stext.normalize('NFC'), or similar utilities in your language of choice. - Using a text editor that supports normalization conversion (some editors have built-in Unicode normalization features or plugins).
- Running a batch conversion on your HTML files before deployment as part of your build process.
Examples
Incorrect: Attribute value uses NFD (decomposed form)
In this example, the id attribute value for "résumé" uses decomposed characters (base letter + combining accent), which triggers the validation error. The decomposition is invisible in source code but present at the byte level.
<!-- The "é" here is stored as "e" + combining acute accent (NFD) -->
<div id="résumé">
<p>My résumé content</p>
</div>
Correct: Attribute value uses NFC (precomposed form)
Here, the id attribute value uses precomposed characters, which is the correct NFC form.
<!-- The "é" here is stored as a single precomposed character (NFC) -->
<div id="résumé">
<p>My résumé content</p>
</div>
While these two examples look identical in source view, they differ at the byte level. You can verify the normalization form using browser developer tools or a hex editor.
Checking and fixing with JavaScript
You can programmatically normalize attribute values:
<script>
// Check if a string is in NFC
const text = "résumé";
const nfcText = text.normalize("NFC");
console.log(text === nfcText); // false if original was NFD
</script>
Checking and fixing with Python
import unicodedata
text = "r\u0065\u0301sume\u0301" # NFD form
normalized = unicodedata.normalize('NFC', text)
print(normalized) # Outputs NFC form: "résumé"
If you encounter this validation error, inspect the flagged attribute value carefully and ensure all characters are in their precomposed NFC form. Adding a normalization step to your build pipeline is a reliable way to prevent this issue from recurring.
The charset meta tag specifies an encoding name that is not the preferred form. Use utf-8 (with a hyphen) instead of utf8.
The HTML specification requires that character encoding declarations use the preferred IANA encoding name. For the Unicode UTF-8 encoding, the preferred name is utf-8, not utf8, UTF8, or other variations. While browsers may still recognize non-preferred names, the W3C validator flags them because the WHATWG HTML standard and IANA character set registry both list utf-8 as the canonical form.
This applies to the <meta charset> declaration and, less commonly, to charset parameters in Content-Type headers or <meta http-equiv> tags.
Incorrect example
<meta charset="utf8">
Correct example
<meta charset="utf-8">
The HTML specification defines specific rules about which autocomplete autofill field names can be paired with which input types. The tel-national token (which represents a phone number without the country code) is classified as requiring a text-based input control. Meanwhile, <input type="tel"> is a specialized control that the spec treats differently from a plain text field. When the validator encounters tel-national on a type="tel" input, it flags the mismatch because the autofill field name is not allowed in that context.
This might seem counterintuitive — a national telephone number value on a telephone input feels like a natural fit. However, the distinction exists because type="tel" already implies a complete telephone number, and the spec maps the broader tel autocomplete token to it. The more granular telephone tokens like tel-national, tel-country-code, tel-area-code, tel-local, tel-local-prefix, and tel-local-suffix are designed for type="text" inputs where a phone number is being broken into individual parts across multiple fields.
Getting this right matters for browser autofill behavior. When the autocomplete value and input type are properly paired according to the spec, browsers can more reliably populate the field with the correct portion of the user's stored phone number. An invalid pairing may cause autofill to silently fail or behave unpredictably across different browsers.
How to fix it
You have two options:
- Change the input type to
text— Usetype="text"if you specifically want the national portion of a phone number (without the country code). This is the right choice when you're splitting a phone number across multiple fields. - Change the autocomplete value to
tel— Useautocomplete="tel"if you want a single field for the full phone number. This pairs correctly withtype="tel".
Examples
❌ Invalid: tel-national on type="tel"
<label for="phone">Phone number</label>
<input id="phone" name="phone" type="tel" autocomplete="tel-national">
This triggers the validation error because tel-national is not allowed on a type="tel" input.
✅ Fix option 1: Change input type to text
<label for="phone">Phone number (without country code)</label>
<input id="phone" name="phone" type="text" autocomplete="tel-national">
Using type="text" satisfies the spec's requirement for the tel-national autofill token. This is ideal when collecting just the national portion of a number.
✅ Fix option 2: Change autocomplete to tel
<label for="phone">Phone number</label>
<input id="phone" name="phone" type="tel" autocomplete="tel">
Using autocomplete="tel" is the correct pairing for type="tel" and tells the browser to autofill the complete phone number.
✅ Splitting a phone number across multiple fields
When you need separate fields for different parts of a phone number, use type="text" with the granular autocomplete tokens:
<fieldset>
<legend>Phone number</legend>
<label for="country-code">Country code</label>
<input id="country-code" name="country-code" type="text" autocomplete="tel-country-code">
<label for="national">National number</label>
<input id="national" name="national" type="text" autocomplete="tel-national">
</fieldset>
The xml:lang attribute is a holdover from XHTML, where it was the standard way to declare the language of an element. In HTML5 (the text/html serialization), the lang attribute is the proper way to specify language. The HTML specification allows xml:lang for compatibility purposes, but only if it is accompanied by a lang attribute with an identical value. If xml:lang appears alone, or if its value doesn't match the lang attribute, the document is non-conforming.
This matters for several reasons. Screen readers and other assistive technologies rely on the lang attribute—not xml:lang—to determine pronunciation and language-specific behavior. Search engines also use lang for content indexing and language detection. Having xml:lang without lang means the language declaration may be ignored entirely, degrading both accessibility and SEO.
In modern HTML5 documents, there is rarely a reason to include xml:lang at all. The lang attribute alone covers all use cases. The only scenario where you might need both is if your document must be compatible with both HTML and XHTML parsers (polyglot markup), in which case the two attributes must carry the same value.
How to Fix
You have two options:
- Remove
xml:langand use onlylang(recommended for HTML5 documents). - Add a
langattribute that matches the existingxml:langvalue (for polyglot documents).
If you do keep both attributes, make sure the values are exactly the same—including case and subtags. For example, lang="en-US" must be paired with xml:lang="en-US", not xml:lang="en".
Examples
Incorrect: xml:lang without lang
<html xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p xml:lang="fr">Bonjour le monde</p>
</body>
</html>
This triggers the validation error because both the <html> and <p> elements have xml:lang but no lang attribute.
Incorrect: Mismatched values
<html lang="en" xml:lang="en-US">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Even though both attributes are present, the values "en" and "en-US" don't match, which is also invalid.
Correct: Using only lang (recommended)
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p lang="fr">Bonjour le monde</p>
</body>
</html>
This is the cleanest approach for HTML5 documents. The lang attribute is all you need.
Correct: Both attributes with matching values (polyglot)
<!DOCTYPE html>
<html lang="en" xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p lang="fr" xml:lang="fr">Bonjour le monde</p>
</body>
</html>
If you must keep xml:lang, every element that has it must also have lang with the exact same value.
The alt attribute is one of the most important accessibility features in HTML. When a screen reader encounters an <img> element, it reads the alt text aloud so that visually impaired users understand the image's content and purpose. Without it, screen readers may fall back to reading the file name (e.g., "DSC underscore 0042 dot jpeg"), which is meaningless and confusing. Search engines also use alt text to understand and index image content, so including it benefits SEO as well.
The HTML specification requires the alt attribute on all <img> elements, with only narrow exceptions (such as when the image's role is explicitly overridden via certain ARIA attributes, or when the image is inside a <figure> with a <figcaption> that fully describes it—though even then, including alt is strongly recommended).
How to choose the right alt text
The value of the alt attribute depends on the image's purpose:
- Informative images — Describe the content concisely. For example, a photo of a product should describe the product.
- Functional images — Describe the action or destination, not the image itself. For example, a search icon used as a button should have
alt="Search", notalt="Magnifying glass". - Decorative images — Use an empty
altattribute (alt=""). This tells screen readers to skip the image entirely. Do not omit the attribute—use an empty string. - Complex images (charts, diagrams) — Provide a brief summary in
altand a longer description elsewhere on the page or via a link.
Examples
❌ Missing alt attribute
This triggers the W3C validation error:
<img src="photo.jpg">
A screen reader has no useful information to convey, and the validator flags this as an error.
✅ Informative image with descriptive alt
<img src="photo.jpg" alt="Person holding an orange tabby cat in a sunlit garden">
The alt text describes what the image shows, giving screen reader users meaningful context.
✅ Decorative image with empty alt
<img src="decorative-border.png" alt="">
When an image is purely decorative and adds no information, an empty alt attribute tells assistive technology to ignore it. This is valid and preferred over omitting the attribute.
✅ Functional image inside a link
<a href="/home">
<img src="logo.svg" alt="Acme Corp — Go to homepage">
</a>
Because the image is the only content inside the link, the alt text must describe the link's purpose.
✅ Image inside a <figure> with <figcaption>
<figure>
<img src="chart.png" alt="Bar chart showing quarterly revenue growth from Q1 to Q4 2024">
<figcaption>Quarterly revenue growth for fiscal year 2024.</figcaption>
</figure>
Even when a <figcaption> is present, including a descriptive alt attribute is best practice. The alt should describe the image itself, while the <figcaption> provides additional context visible to all users.
Common mistakes to avoid
- Don't start with "Image of" or "Picture of" — Screen readers already announce the element as an image. Write
alt="Golden retriever playing fetch", notalt="Image of a golden retriever playing fetch". - Don't use the file name —
alt="IMG_4392.jpg"is not helpful. - Don't duplicate surrounding text — If the text next to the image already describes it, use
alt=""to avoid redundancy. - Don't omit
altthinking it's optional — A missingaltattribute andalt=""are fundamentally different. Missing means the screen reader may announce the file path; empty means the screen reader intentionally skips the image.
The autocomplete="new-password" value can only be used on <input> elements whose type accepts password input, specifically type="password".
The autocomplete attribute helps browsers autofill form fields. However, certain autofill tokens are restricted to specific input types. The new-password token tells the browser to suggest a new, generated password — which only makes sense on a password field. If you use it on a type="text", type="email", or other non-password input, the validator will flag it as invalid.
The same restriction applies to current-password. Both tokens are exclusively valid on <input type="password">.
Invalid Example
<label for="pass">Create a password</label>
<input type="text" id="pass" autocomplete="new-password">
Valid Example
<label for="pass">Create a password</label>
<input type="password" id="pass" autocomplete="new-password">
If your field is not meant to collect a password, use a different autocomplete value appropriate for the input type, such as username, email, or off.
The HTML specification defines specific rules about which autocomplete values can be used on which form elements. The street-address token is categorized as a "multiline" autofill field because street addresses often span multiple lines (e.g., "123 Main St\nApt 4B"). Since <input> elements only accept single-line text, the spec prohibits using street-address with them. The <textarea> element, on the other hand, naturally supports multiline content, making it the appropriate host for this token.
This matters for several reasons. First, browsers use autocomplete values to offer autofill suggestions. When the element type doesn't match the expected data format, browsers may not autofill correctly or may ignore the hint entirely. Second, standards compliance ensures consistent behavior across different browsers and assistive technologies. Third, using the correct pairing improves the user experience — users expect their full street address to appear in a field that can actually display it properly.
You have two approaches to fix this:
Use a
<textarea>— If you want the full street address in a single field, switch from<input>to<textarea>. This is the most semantically correct choice when you expect multiline address data.Use line-specific tokens on
<input>elements — If your form design uses separate single-line fields for each part of the address, useaddress-line1,address-line2, andaddress-line3instead. These tokens are explicitly allowed on<input>elements.
Examples
❌ Invalid: street-address on an <input>
<label for="address">Street Address</label>
<input type="text" id="address" name="address" autocomplete="street-address">
This triggers the validation error because street-address requires a multiline control.
✅ Fix: Use a <textarea> with street-address
<label for="address">Street Address</label>
<textarea id="address" name="address" autocomplete="street-address"></textarea>
The <textarea> supports multiline text, so street-address is valid here.
✅ Fix: Use line-specific tokens on <input> elements
<label for="address1">Address Line 1</label>
<input type="text" id="address1" name="address1" autocomplete="address-line1">
<label for="address2">Address Line 2</label>
<input type="text" id="address2" name="address2" autocomplete="address-line2">
The address-line1, address-line2, and address-line3 tokens are single-line autofill fields and are perfectly valid on <input> elements. This approach is common in forms that break the address into separate fields for apartment numbers, building names, or other details.
Summary of allowed pairings
| Token | <input> | <textarea> |
|---|---|---|
street-address | ❌ Not allowed | ✅ Allowed |
address-line1 | ✅ Allowed | ✅ Allowed |
address-line2 | ✅ Allowed | ✅ Allowed |
address-line3 | ✅ Allowed | ✅ Allowed |
Choose the approach that best fits your form layout. If you prefer a single address field, use <textarea> with street-address. If you prefer structured, separate fields, use <input> elements with the appropriate address-line tokens.
In URLs, percent-encoding is used to represent special or reserved characters. The format is a % sign followed by exactly two hexadecimal digits (0–9, A–F), such as %20 for a space or %3F for a question mark. When the browser encounters a % in a URL, it expects the next two characters to be valid hex digits. If they aren't — for example, % followed by a letter like G, a non-hex character, or nothing at all — the URL is considered malformed.
This issue specifically targets the srcset attribute on <source> elements (commonly used inside <picture> elements), where one or more image candidate URLs contain an invalid percent sequence. The most common causes are:
- A literal
%sign in a query parameter — e.g.,?quality=80%where%is meant literally but isn't encoded. - Truncated percent-encoding — e.g.,
%2instead of%2F, possibly from a copy-paste error or a broken URL-encoding function. - Double-encoding gone wrong — a URL that was partially encoded, leaving some
%characters in an ambiguous state.
This matters because browsers may handle malformed URLs inconsistently. Some browsers might try to recover gracefully, while others may fail to load the image entirely. Invalid URLs also break standards compliance, can cause issues with CDNs and caching layers, and make your markup unreliable across different environments.
How to fix it
- Find the offending
%character in yoursrcsetURL. - If the
%is meant literally (e.g., as part of a percentage value like80%), encode it as%25. - If the
%is part of an incomplete percent-encoding (e.g.,%2instead of%2F), correct it to the full three-character sequence. - Review your URL-generation logic — if URLs are built dynamically by a CMS, template engine, or server-side code, ensure proper encoding is applied before output.
Examples
Invalid: literal % in srcset URL
The % in quality=80% is not followed by two hex digits, so it's treated as a broken percent-encoding sequence.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Valid: % encoded as %25
Replacing the bare % with %25 produces a valid URL.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%25"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Invalid: truncated percent-encoding in a file path
Here, %2 is incomplete — it should be %2F (which decodes to /) or some other valid sequence.
<picture>
<source
srcset="https://example.com/images%2photo.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Valid: corrected percent-encoding
The sequence is now %2F, a properly formed encoding.
<picture>
<source
srcset="https://example.com/images%2Fphoto.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Invalid: unencoded % in srcset with multiple candidates
Every URL in the srcset list must be valid. Here, both candidates contain a bare %.
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75% 480w,
https://example.com/img.webp?w=800&q=75% 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Valid: all candidates properly encoded
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75%25 480w,
https://example.com/img.webp?w=800&q=75%25 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Note that in the corrected example, & is also encoded as & within the HTML attribute, which is required for valid markup when using ampersands in attribute values.
An element with role="row" must sit inside an element whose role is table, grid, treegrid, or rowgroup, or be claimed by one through aria-owns. A row on its own has no structure to belong to, so assistive technologies cannot tell which table or grid it is part of.
The row role describes one row of a composite structure. Those four allowed owners fall into two groups: table and rowgroup for static tabular data, and grid and treegrid for interactive widgets that support keyboard navigation. In every case the container supplies the context a screen reader needs to announce the row's position and its cells. Placed outside such a container, the row is orphaned and the whole table semantics break down.
The most reliable fix is to nest the role="row" element inside its container in the DOM. When your layout prevents that, keep the row where it is and point aria-owns from the container at the row's id.
Invalid example
The role="row" is a sibling of the table, so nothing contains or owns it:
<div role="table" aria-label="Prices"></div>
<div role="row">
<span role="cell">Basic</span>
<span role="cell">$9</span>
</div>
Valid example
Nest the row inside the element with role="table":
<div role="table" aria-label="Prices">
<div role="row">
<span role="cell">Basic</span>
<span role="cell">$9</span>
</div>
</div>
If the DOM nesting is not possible, leave the row in place and add aria-owns="price-row" to the container so assistive technologies still treat it as the owner.
The lang attribute on the <html> element tells browsers, screen readers, and search engines what language the page content is written in. Its value must follow the BCP 47 standard, which uses ISO 639 language codes as the primary language subtag. When the validator reports that a language subtag "is not a valid ISO language part of a language tag," it means the code you provided doesn't match any recognized language in the ISO 639 registry.
Common Causes
Typos in the language code
A simple misspelling like emg instead of en, or fre instead of fr, will trigger this error.
Using a country code instead of a language code
Country codes (ISO 3166) and language codes (ISO 639) are different standards. For example, uk is the country code for the United Kingdom, but it's also the valid language code for Ukrainian. Using gb (Great Britain) as a language would be invalid. Similarly, us is not a language code — you need en for English or en-US for American English specifically.
Using made-up or deprecated codes
Codes like xx, en-UK, or other non-standard values will fail validation. Note that while en-US and en-GB are valid (language-region format), en-UK is not because UK is not the correct ISO 3166-1 region subtag for the United Kingdom — GB is.
Why This Matters
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation rules and voice profile. An invalid language code can cause assistive technology to mispronounce content or fall back to a default language. - SEO: Search engines use the
langattribute as a signal for serving the right content to users in the appropriate language and region. - Browser behavior: Browsers use the language tag for spell-checking, hyphenation, font selection, and other language-sensitive rendering decisions.
How to Fix It
- Identify the language your page is written in.
- Look up the correct ISO 639-1 two-letter code (preferred) or ISO 639-2 three-letter code for that language.
- If you need to specify a regional variant, append a hyphen and the ISO 3166-1 region code (e.g.,
pt-BRfor Brazilian Portuguese). - Replace the invalid value in the
langattribute.
Some commonly used valid language codes:
| Language | Code |
|---|---|
| English | en |
| English (US) | en-US |
| English (UK) | en-GB |
| Spanish | es |
| French | fr |
| German | de |
| Portuguese (Brazil) | pt-BR |
| Chinese (Simplified) | zh-Hans |
| Japanese | ja |
| Arabic | ar |
Examples
❌ Invalid: Typo in language code
<html lang="emg">
❌ Invalid: Country code used instead of language code
<html lang="us">
❌ Invalid: Incorrect region subtag
<html lang="en-UK">
❌ Invalid: Made-up language code
<html lang="english">
✅ Valid: Correct two-letter language code
<html lang="en">
✅ Valid: Language with region subtag
<html lang="en-GB">
✅ Valid: Full document with proper lang attribute
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Ma page</title>
</head>
<body>
<p>Bonjour le monde !</p>
</body>
</html>
You can verify your language tag using the IANA Language Subtag Registry or the BCP 47 Language Subtag Lookup tool to ensure your code is valid before updating your markup.
The lang attribute on any HTML element must be a valid IANA language subtag (like en, fr, or ja), not a programming language name.
The lang attribute specifies the natural (human) language of the element's content, used by browsers, screen readers, and search engines for accessibility and localization purposes. Values like csharp, javascript, or python are not valid because they are not human languages registered in the IANA Language Subtag Registry.
This issue commonly arises when using <code lang="csharp"> to indicate the programming language of a code snippet. While the intention makes sense, lang is not the right attribute for this purpose.
To indicate a programming language on a <code> element, the recommended convention from the HTML specification is to use the class attribute with a language- prefix (e.g., class="language-csharp"). This is also the pattern used by popular syntax highlighting libraries like Prism.js and highlight.js.
If the code content is written in a specific human language (like English), you can still use lang="en" alongside the class.
Bad Example
<pre>
<code lang="csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
Good Example
<pre>
<code class="language-csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
The X-UA-Compatible meta tag was originally introduced to control which rendering engine Internet Explorer would use to display a page. Developers could force IE to emulate older versions (e.g., IE=7, IE=9) or use the latest available engine with IE=edge. The value IE=edge,chrome=1 was also commonly used to activate the Google Chrome Frame plugin, which allowed Internet Explorer to use Chrome's rendering engine instead.
The HTML specification now only permits the value IE=edge for this meta tag. Other values are considered invalid for several reasons:
- Google Chrome Frame is discontinued. The
chrome=1directive targeted a plugin that was retired in February 2014 and is no longer supported by any browser. - Legacy IE rendering modes are obsolete. Internet Explorer itself has been retired, making emulation modes like
IE=EmulateIE7orIE=9pointless. - Standards compliance. The WHATWG HTML living standard explicitly requires the
contentattribute value to beIE=edgewhenhttp-equiv="X-UA-Compatible"is used.
In practice, since all modern browsers use their latest rendering engine by default, this meta tag has little functional impact today. If your site no longer needs to support Internet Explorer at all, you can safely remove the tag entirely. If you choose to keep it — for example, in environments where legacy IE browsers might still access your site — ensure the value is exactly IE=edge.
Examples
Invalid: Using chrome=1 with IE=edge
This was a common pattern when Google Chrome Frame was active, but it now triggers a validation error:
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
Invalid: Using a legacy IE rendering mode
Forcing a specific IE version is no longer valid:
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
Invalid: Specifying a particular IE version
<meta http-equiv="X-UA-Compatible" content="IE=9">
Valid: Using IE=edge
The only accepted value is IE=edge:
<meta http-equiv="X-UA-Compatible" content="IE=edge">
Valid: Removing the tag entirely
If you don't need Internet Explorer compatibility, the simplest fix is to remove the meta tag altogether. A minimal valid document without it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The listbox role is the implicit ARIA role for a <select> element only when it has a multiple attribute or a size attribute greater than 1. A standard single-selection <select> (dropdown) has an implicit role of combobox, so explicitly assigning role="listbox" to it creates a conflict.
When a <select> element has no multiple attribute and no size greater than 1, browsers render it as a collapsed dropdown — a combobox. The listbox role describes a widget where all options are persistently visible, which matches the behavior of a multi-select or a select with a visible size greater than 1. Applying role="listbox" to a standard dropdown misrepresents the control to assistive technologies.
You have a few options to fix this: remove the role="listbox" entirely (since the browser already assigns the correct implicit role), add the multiple attribute, or set size to a value greater than 1.
Incorrect Example
<select role="listbox" name="color">
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
</select>
Fixed Examples
Remove the explicit role and let the browser handle it:
<select name="color">
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
</select>
Or, if you genuinely need role="listbox", use multiple or size greater than 1:
<select role="listbox" name="color" multiple>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
</select>
<select role="listbox" name="color" size="3">
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
</select>
In most cases, simply removing role="listbox" is the best fix. The implicit ARIA roles already convey the correct semantics to assistive technologies without any extra attributes.
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries