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 autocomplete attribute on <form> elements controls whether the browser should automatically fill in form fields based on previously entered data. Unlike autocomplete on <input> elements — which accepts a rich set of tokens like "name", "email", "street-address", etc. — the <form> element itself only accepts two values: "on" and "off".
A common workaround that gained popularity was setting autocomplete="nope" (or other made-up values like "new-password", "false", or "disabled") on the <form> element. This hack exploited the fact that some browsers would treat any unrecognized value as a signal to disable autocomplete. However, this behavior is non-standard and unreliable — browsers may ignore invalid values entirely and fall back to their default behavior, which is typically "on".
Using invalid attribute values causes several problems:
- Standards compliance: The HTML specification explicitly defines the allowed values, and validators will flag anything else as an error.
- Unpredictable behavior: Different browsers handle invalid values differently. What works in one browser today may stop working in the next update.
- Accessibility and user experience: Assistive technologies and browser features rely on standard attribute values to function correctly. Invalid values can interfere with password managers and autofill tools that many users depend on.
It's worth noting that even with autocomplete="off", some browsers (particularly Chrome) may still autofill certain fields like login credentials for security reasons. If you need finer-grained control, apply autocomplete attributes directly on individual <input> elements using the appropriate autofill tokens from the specification.
Examples
❌ Invalid: Using a made-up value on a form
<formautocomplete="nope"action="/submit"method="post">
<labelfor="username">Username</label>
<inputtype="text"id="username"name="username">
<buttontype="submit">Submit</button>
</form>
This triggers the error Bad value "nope" for attribute "autocomplete" on element "form" because "nope" is not a valid autocomplete value for <form>.
✅ Fixed: Using the correct value to disable autocomplete
<formautocomplete="off"action="/submit"method="post">
<labelfor="username">Username</label>
<inputtype="text"id="username"name="username">
<buttontype="submit">Submit</button>
</form>
✅ Using autocomplete on individual inputs for more control
If you need to disable autocomplete for specific fields while leaving others enabled, apply the attribute directly on the <input> elements instead:
<formaction="/submit"method="post">
<labelfor="username">Username</label>
<inputtype="text"id="username"name="username"autocomplete="username">
<labelfor="secret">One-time code</label>
<inputtype="text"id="secret"name="secret"autocomplete="off">
<buttontype="submit">Submit</button>
</form>
In this example, the username field uses the standard "username" autofill token to help browsers fill it correctly, while the one-time code field has autocomplete disabled since its value should never be reused.
The autocomplete attribute tells the browser how to handle autofill for a form field. The HTML specification defines a strict set of valid values, which include "on", "off", and a list of autofill field names such as "username", "new-password", "cc-number", "postal-code", and many others. When you use a value that isn't in this list — such as "nope", "false", "none", or any other made-up string — the W3C validator reports it as an invalid autofill field name.
A common reason developers use values like "nope" is as a workaround because some browsers historically ignored autocomplete="off". In older versions of Chrome and Firefox, the browser would still show autofill suggestions even when off was set, so developers discovered that using an unrecognized value like "nope" effectively tricked the browser into not showing suggestions. While this hack may have worked in practice, it produces invalid HTML and is not a reliable long-term solution since browser behavior around unrecognized values can change at any time.
Why this matters
- Standards compliance: Invalid attribute values make your HTML non-conforming, which can cause issues with tooling, testing pipelines, and accessibility auditors.
- Accessibility: Screen readers and assistive technologies rely on valid
autocompletevalues to help users fill in forms. Using a correct autofill field name like"given-name"or"email"can significantly improve the experience for users with disabilities. In fact, WCAG 2.1 Success Criterion 1.3.5 specifically recommends using valid autocomplete values for fields that collect user information. - Browser behavior: Modern browsers have improved their handling of
autocomplete="off". Using the standard value is now more reliable than it once was, and using it correctly ensures predictable behavior across browsers.
How to fix it
- To disable autocomplete, replace the invalid value with
"off". - To enable smart autofill, use the appropriate autofill field name from the HTML specification's list of autofill field names. This is the preferred approach for most user-facing forms.
- For new passwords (e.g., registration or password-change forms), use
"new-password"— this tells the browser to suggest a generated password rather than filling in a saved one.
Examples
Invalid: made-up autocomplete value
<inputtype="text"name="firstName"autocomplete="nope">
Other common invalid values that trigger the same error include "false", "none", "disable", and "no".
Fixed: disabling autocomplete with "off"
<inputtype="text"name="firstName"autocomplete="off">
Fixed: using a valid autofill field name
Using a specific autofill field name is often better than "off" because it helps browsers and assistive technologies understand the purpose of the field:
<inputtype="text"name="firstName"autocomplete="given-name">
Fixed: common valid autocomplete values in a form
<formmethod="post"action="/register">
<labelfor="name">Full Name</label>
<inputtype="text"id="name"name="name"autocomplete="name">
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email"autocomplete="email">
<labelfor="newpass">Password</label>
<inputtype="password"id="newpass"name="password"autocomplete="new-password">
<labelfor="tel">Phone</label>
<inputtype="tel"id="tel"name="phone"autocomplete="tel">
<buttontype="submit">Register</button>
</form>
Some of the most commonly used valid values include: "name", "given-name", "family-name", "email", "username", "new-password", "current-password", "street-address", "postal-code", "country", "tel", "cc-number", and "organization". Refer to the full list in the HTML specification for all available options.
The autocomplete attribute tells browsers whether and how to autofill a form field. The HTML specification defines a strict set of valid values for this attribute, known as autofill field names. These include values like "on", "off", "name", "email", "username", "new-password", "current-password", "address-line1", "postal-code", "cc-number", and many others. When you use a value that doesn't appear in this list — such as "nothanks", "nope", or "false" — the W3C validator reports it as an invalid autofill field name.
A common reason developers use made-up values is frustration with browsers ignoring autocomplete="off". Some browsers (notably Chrome) may still autofill certain fields even when autocomplete="off" is set, particularly for login-related fields. This has led to workarounds using random strings, but these are non-standard and can produce unpredictable behavior across different browsers and assistive technologies.
Why This Matters
- Standards compliance: Invalid attribute values make your HTML non-conforming, which can lead to unexpected browser behavior now or in the future.
- Accessibility: Screen readers and other assistive technologies may use the
autocompleteattribute to help users fill in forms. A recognized value like"name"or"email"gives these tools meaningful context, while a random string provides none. - Browser behavior: Browsers are designed to interpret the standard values. An unrecognized value may be treated inconsistently — some browsers might ignore it, others might treat it as
"on", and behavior could change between versions.
How to Fix It
If you want to disable autocomplete, use "off":
<inputtype="text"name="search"autocomplete="off">
If you want to help browsers autofill correctly, use the appropriate autofill field name from the specification:
<inputtype="email"name="email"autocomplete="email">
If autocomplete="off" isn't being respected by the browser (a known issue with some login fields in Chrome), consider these standards-compliant alternatives:
- Use
autocomplete="new-password"on password fields where you don't want saved passwords suggested. - Use a more specific valid token that doesn't match what the browser is trying to autofill.
- Use the
readonlyattribute and remove it on focus via JavaScript as a supplementary measure.
Examples
❌ Invalid: arbitrary string as autocomplete value
<form>
<labelfor="firstName">First name</label>
<inputtype="text"name="firstName"id="firstName"autocomplete="nothanks">
<labelfor="userEmail">Email</label>
<inputtype="email"name="userEmail"id="userEmail"autocomplete="nope">
</form>
Both "nothanks" and "nope" are not valid autofill field names and will trigger the validation error.
✅ Valid: using "off" to disable autocomplete
<form>
<labelfor="firstName">First name</label>
<inputtype="text"name="firstName"id="firstName"autocomplete="off">
<labelfor="userEmail">Email</label>
<inputtype="email"name="userEmail"id="userEmail"autocomplete="off">
</form>
✅ Valid: using proper autofill field names
<form>
<labelfor="firstName">First name</label>
<inputtype="text"name="firstName"id="firstName"autocomplete="given-name">
<labelfor="userEmail">Email</label>
<inputtype="email"name="userEmail"id="userEmail"autocomplete="email">
<labelfor="newPass">New password</label>
<inputtype="password"name="newPass"id="newPass"autocomplete="new-password">
</form>
Using descriptive autofill tokens like "given-name", "email", and "new-password" is the best approach when you want browsers and assistive technologies to understand your form fields. For a complete list of valid autofill field names, refer to the WHATWG HTML specification's autofill section.
The HTML <input> element accepts a specific set of values for its type attribute, as defined by the HTML specification. The value "numeric" is not one of them. When a browser encounters an unrecognized type value, it falls back to type="text", meaning the user gets a plain text field instead of a dedicated numeric input. This fallback behavior means you lose built-in features like increment/decrement spinner controls, numeric keyboard on mobile devices, and native client-side validation that rejects non-numeric entries.
This confusion often arises because of the inputmode="numeric" attribute, which is valid and controls which virtual keyboard appears on mobile devices. The inputmode attribute and the type attribute serve different purposes, and mixing them up leads to this validation error.
Using the correct type="number" value matters for several reasons:
- Accessibility: Screen readers and assistive technologies use the
typeattribute to announce the expected input format to users. - User experience: Browsers display appropriate controls (spinners, numeric keypads) for
type="number"inputs. - Validation: Native form validation automatically checks that the entered value is a valid number, within optional
min/maxbounds and matching an optionalstepvalue. - Standards compliance: Invalid
typevalues cause W3C validation errors and signal potential bugs in your markup.
Examples
Incorrect: using type="numeric"
This triggers the validation error because "numeric" is not a valid type value:
<labelfor="quantity">Quantity:</label>
<inputtype="numeric"id="quantity"name="quantity">
The browser will treat this as type="text", so users can type any characters, no spinner controls appear, and no numeric validation occurs.
Correct: using type="number"
Replace "numeric" with "number":
<labelfor="quantity">Quantity:</label>
<inputtype="number"id="quantity"name="quantity"min="1"max="10">
This gives you a proper numeric input with optional min, max, and step attributes for constraining the allowed range and increments.
Alternative: using inputmode="numeric" with type="text"
If you need a numeric keyboard on mobile but want more control over the input (for example, accepting values like zip codes or credit card numbers that aren't truly "numbers"), you can use inputmode="numeric" on a text input instead:
<labelfor="zip">ZIP Code:</label>
<inputtype="text"inputmode="numeric"pattern="[0-9]{5}"id="zip"name="zip">
Here, type="text" is valid, inputmode="numeric" triggers the numeric keyboard on mobile devices, and the pattern attribute provides validation. This approach is useful when type="number" isn't appropriate — for instance, type="number" strips leading zeros and allows scientific notation like 1e5, which is undesirable for codes and identifiers.
Summary of the fix
| Before (invalid) | After (valid) | Use case |
|---|---|---|
type="numeric" | type="number" | Actual numeric values (quantities, prices, ages) |
type="numeric" | type="text" inputmode="numeric" | Numeric-looking codes (ZIP, PIN, credit card) |
In most cases, simply changing type="numeric" to type="number" is the correct fix. Choose the inputmode approach only when you specifically need a text field with a numeric keyboard.
This error is almost always caused by copying and pasting code from a word processor, CMS rich-text editor, blog post, or PDF. These tools often auto-correct straight quotation marks (") into typographic (curly or "smart") quotes (" and "). While curly quotes look nicer in prose, they are not valid as attribute delimiters in HTML. The browser and the W3C validator only recognize the standard straight double quote (", U+0022) or straight single quote (', U+0027) as attribute value delimiters.
When the validator encounters markup like <meta property="og:type", the curly quotes are treated as part of the attribute's value. The validator then sees the value as "og:type" — a string wrapped in literal curly quote characters. Since the property attribute in RDFa (which Open Graph Protocol relies on) expects a term or an absolute URL, and "og:type" is neither, the validator reports the error.
This problem can affect any HTML attribute, but it's especially common with <meta> tags for Open Graph, Twitter Cards, and other social sharing metadata, since developers frequently copy these snippets from documentation or tutorials.
How to fix it
- Find the curly quotes. Look at the
propertyattribute value in your source code. Curly opening quotes (", U+201C) and closing quotes (", U+201D) may look nearly identical to straight quotes in some fonts, so use your editor's find-and-replace feature to search for"and". - Replace them with straight quotes. Swap every
"and"with a standard straight double quote ("). - Disable smart quotes in your editor. If you're writing HTML in a rich-text editor or word processor, turn off the "smart quotes" or "typographic quotes" feature. Better yet, use a dedicated code editor (like VS Code, Sublime Text, or similar) that won't auto-replace quotes.
Examples
Incorrect — curly quotes around the attribute value
<metaproperty="og:type"content="website"/>
The curly " and " characters become part of the value itself, producing the validator error: Bad value "og:type" for attribute property.
Correct — straight quotes around the attribute value
<metaproperty="og:type"content="website"/>
Standard straight double quotes correctly delimit the attribute values, and the validator sees og:type as expected.
Correct — single straight quotes are also valid
<metaproperty='og:type'content='website'/>
Straight single quotes (', U+0027) work just as well as straight double quotes for delimiting HTML attribute values. Use whichever style your project prefers, but be consistent.
Full Open Graph example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
<metaproperty="og:title"content="My Page">
<metaproperty="og:type"content="website">
<metaproperty="og:url"content="https://example.com/">
<metaproperty="og:image"content="https://example.com/image.png">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
All property and content attributes use straight double quotes, ensuring clean validation and correct parsing by social media crawlers.
The shape attribute on an <area> element tells the browser what kind of clickable region to define within an image map. The HTML living standard (WHATWG) strictly limits this attribute to four values:
rect— a rectangle, defined by four coordinates:x1,y1,x2,y2(top-left and bottom-right corners)circle— a circle, defined by three values:x,y,r(center point and radius)poly— a polygon, defined by a series ofx,ycoordinate pairs tracing the shape's verticesdefault— the entire image area (nocoordsneeded)
The value "polygon" is a common mistake — it's intuitive and used in other contexts like SVG — but it is not valid HTML. Browsers may silently fall back to default or ignore the region entirely when they encounter an unrecognized shape value, which means users could lose the ability to click that area of the image map.
Why this matters
- Browser compatibility: Browsers are not required to handle invalid
shapevalues gracefully. While some may guess your intent, others may render the clickable region incorrectly or not at all. - Accessibility: Screen readers and assistive technologies rely on valid markup to convey image map regions to users. An invalid
shapevalue can make a region invisible to these tools, breaking keyboard navigation and alternative text announcements. - Standards compliance: Using valid attribute values ensures your markup behaves predictably across all browsers and passes automated validation checks.
How to fix it
Replace shape="polygon" with shape="poly". The coords attribute syntax stays the same — a comma-separated list of x,y pairs representing each vertex of the polygon, in order. Coordinate values are in CSS pixels relative to the image's intrinsic (natural) dimensions.
Also ensure that each <area> element includes:
- An
hrefattribute to make the region a link - An
altattribute for accessibility (required whenhrefis present)
Examples
Invalid: using shape="polygon"
<imgsrc="plan.png"usemap="#floormap"alt="Floor plan"width="400"height="300">
<mapname="floormap">
<areashape="polygon"coords="10,10,80,10,80,60,10,60"href="room-a.html"alt="Room A">
</map>
The validator reports: Bad value "polygon" for attribute "shape" on element "area".
Fixed: using shape="poly"
<imgsrc="plan.png"usemap="#floormap"alt="Floor plan"width="400"height="300">
<mapname="floormap">
<areashape="poly"coords="10,10,80,10,80,60,10,60"href="room-a.html"alt="Room A">
</map>
Changing "polygon" to "poly" resolves the error. The four coordinate pairs (10,10, 80,10, 80,60, 10,60) define a rectangular polygon. The browser automatically closes the shape by connecting the last point back to the first.
Reference: all valid shape values
<imgsrc="diagram.png"usemap="#shapes"alt="Shape examples"width="500"height="400">
<mapname="shapes">
<!-- Rectangle: top-left (10,10) to bottom-right (100,80) -->
<areashape="rect"coords="10,10,100,80"href="rect.html"alt="Rectangle region">
<!-- Circle: center (200,60), radius 40 -->
<areashape="circle"coords="200,60,40"href="circle.html"alt="Circle region">
<!-- Polygon: triangle with three vertices -->
<areashape="poly"coords="350,10,400,80,300,80"href="triangle.html"alt="Triangle region">
<!-- Default: covers any area not claimed by other regions -->
<areashape="default"href="elsewhere.html"alt="Everything else">
</map>
This example demonstrates all four valid shape values with their corresponding coords syntax. Note that default does not require a coords attribute since it covers the remainder of the image.
The Pragma HTTP header is a holdover from HTTP/1.0, originally used to instruct proxies and browsers not to cache a response. In older HTML specifications, developers sometimes placed <meta http-equiv="Pragma" content="no-cache"> in their documents, hoping browsers would treat it like a real HTTP header. However, the HTML living standard (maintained by WHATWG) restricts the http-equiv attribute to a small set of recognized values: content-type, default-style, refresh, x-ua-compatible, and content-security-policy. The value Pragma is not among them, which is why the W3C validator flags it as invalid.
Beyond the validation error, this approach has always been unreliable. Browsers and caching proxies generally ignore http-equiv meta tags for cache control purposes — they rely on actual HTTP response headers sent by the server. Keeping this invalid tag in your HTML provides a false sense of security while producing no real caching benefit.
Why this is a problem
- Standards compliance: Using an unrecognized
http-equivvalue produces invalid HTML that fails W3C validation. - No practical effect: Most browsers and all intermediary proxies (CDNs, reverse proxies) ignore cache directives embedded in
<meta>tags. Only real HTTP headers reliably control caching behavior. - Misleading code: Developers maintaining the codebase may assume caching is properly managed in HTML when it isn't, leading to unexpected caching issues in production.
How to fix it
- Remove the invalid
<meta>tag from your HTML<head>. - Configure caching via HTTP response headers on your server. Set
Cache-Control(and optionallyPragmafor HTTP/1.0 compatibility) as actual headers in your server configuration or application code.
For example, in an Apache .htaccess file:
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Or in an Nginx configuration:
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
These server-side headers are the correct and effective way to manage caching.
Examples
Incorrect: using Pragma in http-equiv
This triggers the validation error:
<head>
<metacharset="utf-8">
<metahttp-equiv="Pragma"content="no-cache">
<title>My Page</title>
</head>
Incorrect: using Cache-Control in http-equiv
While Cache-Control is also sometimes seen in <meta> tags, it is not a valid http-equiv value in the HTML standard either, and browsers largely ignore it:
<head>
<metacharset="utf-8">
<metahttp-equiv="Cache-Control"content="no-cache">
<title>My Page</title>
</head>
Correct: remove the invalid tag and use server headers
Simply remove the caching meta tag. Your HTML stays clean and valid:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Cache control is handled by server-side HTTP headers.</p>
</body>
</html>
Then configure your web server to send the appropriate Cache-Control and Pragma HTTP response headers as shown in the server configuration examples above. This is the only reliable way to control how browsers and proxies cache your pages.
The HTML specification defines a specific list of allowed ARIA roles for each element. For the <img> element, role="none" is permitted but role="presentation" is not listed as a valid value. This distinction exists even though the WAI-ARIA 1.1 specification treats none and presentation as synonymous — role="none" was introduced as an alias specifically because the word "presentation" was often misunderstood by authors. The HTML spec adopted none as the canonical value for <img>.
Why this matters
Standards compliance: Using a role value not permitted by the HTML specification for a given element produces a validation error. Keeping your HTML valid ensures predictable behavior across browsers and assistive technologies.
Accessibility: The intended purpose of role="presentation" or role="none" is to tell assistive technologies that an element is purely decorative and carries no semantic meaning. However, for images, the established and most reliable way to achieve this is simply providing an empty alt attribute (alt=""). Screen readers already know to skip images with alt="", so adding a role is usually unnecessary.
Clarity of intent: Using alt="" clearly communicates to both browsers and developers that the image is decorative. If the image actually conveys information, it should have a meaningful alt value and no presentation-related role at all.
How to fix it
- If the image is decorative: Remove the
roleattribute entirely and ensure the image hasalt="". This is the simplest and most widely supported approach. - If you need an explicit ARIA role: Replace
role="presentation"withrole="none"and keepalt="". - If the image conveys meaning: Remove the role and provide a descriptive
altattribute that explains what the image communicates.
Examples
❌ Bad: using role="presentation" on an <img>
<imgsrc="divider.png"alt=""role="presentation">
This triggers the validation error because presentation is not an allowed role value for <img> in the HTML specification.
✅ Fixed: decorative image with empty alt (preferred)
<imgsrc="divider.png"alt="">
The empty alt attribute is sufficient to tell assistive technologies the image is decorative. No role is needed.
✅ Fixed: decorative image with role="none"
<imgsrc="divider.png"alt=""role="none">
If you explicitly need an ARIA role, role="none" is the valid value for <img>. The empty alt should still be included.
✅ Fixed: meaningful image with descriptive alt
<imgsrc="quarterly-sales.png"alt="Bar chart showing quarterly sales increasing from $2M to $5M in 2024">
If the image communicates information, provide a descriptive alt and do not use a presentation or none role — doing so would hide the image's meaning from assistive technology users.
❌ Bad: meaningful image incorrectly hidden
<imgsrc="quarterly-sales.png"alt="Sales chart"role="presentation">
This is both invalid HTML (wrong role value for <img>) and an accessibility problem — the role would attempt to hide a meaningful image from screen readers.
The role="presentation" attribute is not allowed on the video element because it is an interactive media element that conveys meaningful content to users.
The role="presentation" (or its synonym role="none") is used to tell assistive technologies that an element is purely decorative and has no semantic meaning. However, the HTML specification restricts which elements can use this role. Interactive elements like video, button, input, and a (with href) cannot have their semantics removed because doing so would hide important functionality from users who rely on assistive technologies.
A video element provides media controls and content that users need to interact with. Stripping its semantics would make it invisible or confusing to screen reader users. If the video is truly decorative (like a background video), there are better approaches than using role="presentation".
If the video is decorative or used as a background, you can hide it from assistive technologies entirely using aria-hidden="true". If the video has meaningful content, keep its native semantics and provide a proper accessible label instead.
Bad Example
<videorole="presentation"autoplaymutedloop>
<sourcesrc="background.mp4"type="video/mp4">
</video>
Fixed Example — Decorative/Background Video
<videoaria-hidden="true"autoplaymutedloop>
<sourcesrc="background.mp4"type="video/mp4">
</video>
Fixed Example — Meaningful Video
<videocontrolsaria-label="Product demo walkthrough">
<sourcesrc="demo.mp4"type="video/mp4">
</video>
This error occurs when you use role="presentational" on an HTML element. While the intent is clear — you want to strip the element's implicit ARIA semantics — the value presentational does not exist in the WAI-ARIA specification. It's a common typo or misconception. The two valid roles for this purpose are presentation and none, which are synonyms of each other.
When you apply role="presentation" or role="none" to an element, you're telling assistive technologies (like screen readers) to ignore the element's implicit semantic meaning. For example, a <table> used purely for layout purposes has no tabular data semantics, so role="presentation" removes the table-related semantics from the accessibility tree. However, the content inside the element remains accessible — only the container's semantics (and in some cases, its required associated descendants) are removed.
This matters for several reasons:
- Standards compliance: Using an invalid role value means the attribute is essentially ignored by browsers and assistive technologies, so the element's semantics are not removed as intended.
- Accessibility: If a screen reader encounters an unknown role, it may fall back to the element's default semantics, leading to a confusing experience. For instance, a layout table without a valid
presentationrole may still be announced as a data table. - Browser consistency: Invalid ARIA values can lead to unpredictable behavior across different browsers and assistive technologies.
To fix this, simply replace presentational with presentation or none.
Examples
❌ Incorrect: using the invalid presentational role
<tablerole="presentational">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using presentation
<tablerole="presentation">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using none (synonym for presentation)
<tablerole="none">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
Other elements where this applies
The presentation / none role can be used on various elements, not just tables. Here's another common example with an image used purely for decoration:
<!-- ❌ Incorrect -->
<imgsrc="divider.png"alt=""role="presentational">
<!-- ✅ Correct -->
<imgsrc="divider.png"alt=""role="presentation">
When to use presentation vs. none
Both presentation and none are functionally identical. The none role was introduced later as a clearer name, since presentation can be confused with visual presentation. For maximum browser support, some developers use both:
<tablerole="none presentation">
<tr>
<td>Layout content</td>
</tr>
</table>
This fallback pattern ensures that older assistive technologies that don't recognize none will still pick up presentation. However, in most modern environments, either value alone is sufficient.
Understanding the Issue
MIME types follow a specific format: a type and a subtype separated by a forward slash, such as text/javascript or application/json. When the W3C validator encounters type="rocketlazyloadscript" on a <script> element, it flags it because this value has no slash and no subtype — it doesn't conform to the MIME type syntax defined in the HTML specification.
The value rocketlazyloadscript is intentionally set by the WP Rocket WordPress caching and performance plugin. WP Rocket changes the type attribute of <script> elements from text/javascript (or removes the default) and replaces it with this custom value. This prevents the browser from executing the script immediately on page load. WP Rocket's JavaScript then swaps the type back to a valid value when the script should actually be loaded, achieving a lazy-loading effect that can improve page performance.
Why This Is Flagged
The HTML specification states that if the type attribute is present on a <script> element, its value must be one of the following:
- A valid JavaScript MIME type (e.g.,
text/javascript) — indicating the script should be executed. - The string
module— indicating the script is a JavaScript module. - Any other valid MIME type that is not a JavaScript MIME type — indicating a data block that the browser should not execute.
Since rocketlazyloadscript is not a valid MIME type at all (it lacks the type/subtype structure), it fails validation. While browsers will simply ignore scripts with unrecognized type values (which is exactly what WP Rocket relies on), the markup itself is technically non-conforming.
How to Fix It
Because this value is generated by a plugin rather than authored manually, your options are:
Configure WP Rocket to exclude specific scripts — In WP Rocket's settings under "File Optimization," you can exclude individual scripts from the "Delay JavaScript execution" feature. This prevents WP Rocket from modifying their
typeattribute.Disable delayed JavaScript execution entirely — If W3C compliance is critical, you can turn off the "Delay JavaScript execution" option in WP Rocket. This eliminates the validation errors but sacrifices the performance benefit.
Accept the validation errors — WP Rocket acknowledges these errors in their documentation and considers them an expected side effect of their optimization technique. Since browsers handle the non-standard
typegracefully (by not executing the script), there is no functional or accessibility issue. The errors are purely a standards compliance concern.
Examples
Invalid: Non-standard type value (generated by WP Rocket)
<scripttype="rocketlazyloadscript"src="/js/analytics.js"></script>
The validator reports: Bad value "rocketlazyloadscript" for attribute "type" on element "script": Subtype missing.
Valid: Standard type attribute
<scripttype="text/javascript"src="/js/analytics.js"></script>
Valid: Omitting the type attribute entirely
Since text/javascript is the default for <script> elements in HTML5, the type attribute can be omitted entirely:
<scriptsrc="/js/analytics.js"></script>
Valid: Using a module script
<scripttype="module"src="/js/app.js"></script>
Valid: Using a data block with a proper MIME type
If you need a non-executed data block, use a valid MIME type that isn't a JavaScript type:
<scripttype="application/json"id="config">
{"lazy":true,"threshold":200}
</script>
The rowgroup role represents a group of rows within a tabular structure — similar to what <thead>, <tbody>, or <tfoot> provide in a native HTML <table>. According to the ARIA specification, the rowgroup role should be used on elements that serve as structural containers for rows within a grid, table, or treegrid. A <header> element is not appropriate for this role because it carries its own strong semantic meaning (typically banner or a sectioning header), and overriding it with role="rowgroup" creates a conflict that the W3C validator flags.
This matters for several reasons. First, assistive technologies rely on correct role assignments to convey the structure of a page. When a <header> element is given role="rowgroup", screen readers receive contradictory signals about what the element represents, which can confuse users. Second, the HTML specification restricts which ARIA roles can be applied to certain elements — the <header> element does not allow rowgroup as a valid role override.
To fix this issue, you have two main options:
- Replace the
<header>element with a<div>— since<div>has no implicit ARIA role, it freely acceptsrole="rowgroup". - Use native HTML table elements — replace the custom ARIA table structure with
<table>,<thead>,<tbody>,<tr>,<th>, and<td>, which provide the correct semantics without requiring any ARIA roles.
Examples
❌ Incorrect: role="rowgroup" on a <header> element
<divrole="table"aria-label="Quarterly Sales">
<headerrole="rowgroup">
<divrole="row">
<spanrole="columnheader">Quarter</span>
<spanrole="columnheader">Revenue</span>
</div>
</header>
<divrole="rowgroup">
<divrole="row">
<spanrole="cell">Q1</span>
<spanrole="cell">$1.2M</span>
</div>
</div>
</div>
The validator reports Bad value "rowgroup" for attribute "role" on element "header" because <header> cannot accept the rowgroup role.
✅ Fix option 1: Use a <div> instead of <header>
<divrole="table"aria-label="Quarterly Sales">
<divrole="rowgroup">
<divrole="row">
<spanrole="columnheader">Quarter</span>
<spanrole="columnheader">Revenue</span>
</div>
</div>
<divrole="rowgroup">
<divrole="row">
<spanrole="cell">Q1</span>
<spanrole="cell">$1.2M</span>
</div>
</div>
</div>
A <div> is semantically neutral, so role="rowgroup" is perfectly valid on it.
✅ Fix option 2: Use native HTML table elements
<table>
<caption>Quarterly Sales</caption>
<thead>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<td>Q1</td>
<td>$1.2M</td>
</tr>
</tbody>
</table>
Native table elements like <thead> and <tbody> implicitly carry the rowgroup role, so no ARIA attributes are needed at all. This approach is generally preferred because it provides the best accessibility support out of the box and results in cleaner, more maintainable markup. Only use ARIA table roles when you cannot use native HTML tables (for example, when building a custom grid layout that requires non-table CSS).
Media types describe the general category of a device for which a stylesheet is intended. The most commonly used values are screen (for computer screens, tablets, and phones), print (for print preview and printed pages), and all (the default, for all devices).
Understanding Deprecated Media Types
CSS 2.1 and Media Queries 3 defined several additional media types: tty, tv, projection, handheld, braille, embossed, and aural. All of these were deprecated in the Media Queries 4 specification. The projection type was originally intended for projected presentations (such as slideshows), but modern browsers never meaningfully distinguished between screen and projection rendering contexts.
Why This Is a Problem
- Standards compliance: Using deprecated media types produces validator warnings and means your code doesn't conform to current web standards.
- No practical effect: Modern browsers treat unrecognized or deprecated media types as not matching, which means a stylesheet targeted only at
projectionwould never be applied. When combined withscreenin a comma-separated list (e.g.,screen,projection), theprojectionportion is simply ignored — it adds clutter without benefit. - Maintainability: Keeping deprecated values in your markup can confuse other developers and suggest that the code targets a platform that no longer exists in the spec.
How to Fix It
- Remove the deprecated media type from the
mediaattribute, keeping only valid types likescreen,print,speech, orall. - Remove the
mediaattribute entirely if the remaining value isallor if you only needscreen(sincescreenis the most common rendering context and stylesheets without amediaattribute default toall). - Use modern media features instead of deprecated media types if you need to target specific device capabilities (e.g.,
(hover: none),(pointer: coarse),(display-mode: fullscreen)).
Examples
❌ Incorrect: Using the deprecated projection media type
<linkrel="stylesheet"href="styles.css"media="screen,projection">
This triggers the validation warning because projection has been deprecated.
✅ Correct: Using only the screen media type
<linkrel="stylesheet"href="styles.css"media="screen">
✅ Correct: Removing the media attribute entirely
If you want the stylesheet to apply to all media types (the default behavior), simply omit the attribute:
<linkrel="stylesheet"href="styles.css">
✅ Correct: Combining valid media types
If you need your stylesheet to apply to both screen and print contexts:
<linkrel="stylesheet"href="styles.css"media="screen,print">
❌ Other deprecated media types to avoid
All of the following are deprecated and will produce similar warnings:
<linkrel="stylesheet"href="a.css"media="handheld">
<linkrel="stylesheet"href="b.css"media="tv">
<linkrel="stylesheet"href="c.css"media="braille">
<linkrel="stylesheet"href="d.css"media="embossed">
<linkrel="stylesheet"href="e.css"media="tty">
<linkrel="stylesheet"href="f.css"media="aural">
Replace these with screen, print, speech, all, or use specific media features to target the device characteristics you need.
The search ARIA role is a landmark role, which means it identifies a large, navigable section of a page — specifically, the region that contains the search functionality. Landmark roles help assistive technologies (like screen readers) quickly identify and jump to major sections of a document. Because landmarks describe sections of a page, they belong on container elements that encompass all the parts of the search interface (the label, the input field, the submit button, etc.), not on a single <input> element.
When you place role="search" on an <input>, the validator rejects it because the search role doesn't match the semantics of an input control. An <input> represents a single interactive widget, not a page region. The valid way to indicate that an input field is for search queries is to use <input type="search">, which gives browsers and assistive technologies the correct semantic meaning for that specific control.
Meanwhile, if you want to mark an entire search form as a search landmark, apply role="search" to the <form> element that wraps the search controls. In modern HTML, you can also use the <search> element, which has the implicit search landmark role without needing any ARIA attribute.
How to fix it
- Remove
role="search"from the<input>element. - Change the input's
typeto"search"— this tells browsers and assistive technologies that the field is for search queries. - Apply
role="search"to the wrapping<form>, or use the HTML<search>element as the container.
Examples
❌ Incorrect: role="search" on an <input>
<form>
<labelfor="query">Search</label>
<inputrole="search"id="query"name="q">
<buttontype="submit">Go</button>
</form>
This triggers the validation error because search is not a valid role for <input>.
✅ Correct: type="search" on the input, role="search" on the form
<formrole="search">
<labelfor="query">Search this site</label>
<inputtype="search"id="query"name="q">
<buttontype="submit">Go</button>
</form>
Here, role="search" is correctly placed on the <form> element, creating a search landmark. The <input type="search"> conveys the correct semantics for the input field itself.
✅ Correct: Using the <search> element (modern HTML)
<search>
<form>
<labelfor="query">Search this site</label>
<inputtype="search"id="query"name="q">
<buttontype="submit">Go</button>
</form>
</search>
The <search> element has the implicit ARIA role of search, so no explicit role attribute is needed on either the container or the form. This is the most semantic approach in browsers that support it.
✅ Correct: Standalone search input without a landmark
If you simply need a search-styled input without marking up a full landmark region, just use type="search":
<labelfor="filter">Filter results</label>
<inputtype="search"id="filter"name="filter">
This gives the input the correct semantics and allows browsers to provide search-specific UI features (such as a clear button) without requiring a landmark role.
The value section does not exist in the WAI-ARIA specification. ARIA defines a specific set of role values, and section is not among them. This is likely a confusion between the HTML element name <section> and the ARIA role region, which is the role that the <section> element implicitly maps to. Because section is not a recognized role, the validator rejects it as an invalid value.
This matters for several reasons. First, assistive technologies like screen readers rely on ARIA roles to communicate the purpose of elements to users. An unrecognized role value may be ignored entirely or cause unexpected behavior, degrading the experience for users who depend on these tools. Second, the <section> element already carries native semantics equivalent to role="region" (when it has an accessible name), so adding a redundant or incorrect role provides no benefit and introduces potential problems.
According to the ARIA in HTML specification, you should generally avoid setting a role on elements that already have appropriate native semantics. The <section> element's implicit role is region, so explicitly adding role="region" is redundant in most cases. The simplest and best fix is to remove the role attribute altogether and let the native HTML semantics do their job.
If you do need to override an element's role for a specific design pattern (for example, turning a <section> into a navigation landmark), use a valid ARIA role from the WAI-ARIA specification.
Examples
Incorrect: using the invalid section role
<sectionrole="section">
<h2>About Us</h2>
<p>Learn more about our team.</p>
</section>
This triggers the validation error because section is not a valid ARIA role value.
Correct: remove the role attribute
<section>
<h2>About Us</h2>
<p>Learn more about our team.</p>
</section>
The <section> element already provides the correct semantics. No role attribute is needed.
Correct: use a valid ARIA role if needed
If you have a specific reason to assign a role, use a valid one. For example, if a <section> is being used as a navigation landmark:
<sectionrole="navigation"aria-label="Main navigation">
<ul>
<li><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
</ul>
</section>
In practice, you would typically use a <nav> element instead, which has the navigation role natively. This example simply illustrates that if you do apply a role, it must be a valid ARIA role value.
Correct: explicit region role with an accessible name
If you want to explicitly mark a section as a named region landmark, you can use role="region" along with an accessible name. However, this is redundant when using <section> with an aria-label or aria-labelledby, since the browser already maps it to region:
<!-- Preferred: native semantics handle the role -->
<sectionaria-labelledby="features-heading">
<h2id="features-heading">Features</h2>
<p>Explore our product features.</p>
</section>
<!-- Also valid but redundant -->
<sectionrole="region"aria-labelledby="features-heading">
<h2id="features-heading">Features</h2>
<p>Explore our product features.</p>
</section>
Both are valid HTML, but the first approach is cleaner and follows the principle of relying on native semantics whenever possible.
ARIA defines a closed set of role tokens. Browsers and assistive technologies rely on this fixed list to determine how an element should be announced and how it fits into the page's landmark structure. When a role value falls outside that list, the attribute is effectively ignored. A screen reader encountering role="sidebar" will not recognize the element as a landmark, so users who navigate by landmarks will skip right past it. Automated accessibility testing tools will also flag it as an error, and the validator will reject it outright.
The concept most people mean when they write role="sidebar" is ancillary or tangential content related to the main content of the page. ARIA calls this the complementary role. In semantic HTML, the <aside> element maps to complementary without any explicit role attribute. Using <aside> is the simplest fix and keeps the markup clean.
A few things to keep in mind when choosing the replacement:
- If the sidebar contains related content, promotional links, or supplementary information,
complementary(via<aside>orrole="complementary") is correct. - If the sidebar is actually a set of navigation links, use
<nav>orrole="navigation"instead. Pick the role that matches the content's purpose, not its visual position. - When a page has more than one
complementaryregion, each one needs a distinct accessible name so screen reader users can tell them apart. Usearia-labelledbypointing to a visible heading, oraria-labelif no heading is present. - Do not add
role="complementary"to an<aside>element. The implicit mapping already handles it, and the redundant attribute adds noise without benefit.
Examples
Invalid: non-existent ARIA role
This triggers the validator error because sidebar is not a defined ARIA role.
<divrole="sidebar">
<h2>Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use role="complementary" on a generic container
If you need to keep the <div>, assign the correct ARIA role and provide an accessible name.
<divrole="complementary"aria-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use <aside> for semantic HTML
The <aside> element has an implicit complementary role, so no role attribute is needed.
<asidearia-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</aside>
Multiple complementary regions with distinct labels
When a page has more than one <aside>, give each a unique accessible name so users can distinguish them.
<asidearia-labelledby="filters-title">
<h2id="filters-title">Filter results</h2>
<!-- filter controls -->
</aside>
<asidearia-labelledby="related-title">
<h2id="related-title">Related articles</h2>
<!-- related links -->
</aside>
When the content is navigation, not complementary
A sidebar that contains section links or site links is navigation, not complementary content. Use <nav> instead.
<navaria-label="Section navigation">
<ul>
<li><ahref="#intro">Intro</a></li>
<li><ahref="#examples">Examples</a></li>
<li><ahref="#contact">Contact</a></li>
</ul>
</nav>
Valid ARIA landmark roles
For reference, these are the ARIA landmark roles that browsers and assistive technologies recognize:
banner(implicit on<header>when not nested inside a sectioning element)navigation(implicit on<nav>)main(implicit on<main>)complementary(implicit on<aside>)contentinfo(implicit on<footer>when not nested inside a sectioning element)region(implicit on<section>when it has an accessible name)search(implicit on<search>)form(implicit on<form>when it has an accessible name)
Stick to these defined values. Inventing role names like sidebar, content, or wrapper will always produce a validation error and provide no accessibility benefit.
ARIA defines a fixed set of role values that user agents and assistive technologies understand. sidebar is not in that set, so role="sidebar" fails conformance checking and gives unreliable signals to screen readers. Using a valid role or the correct HTML element improves accessibility, ensures consistent behavior across browsers and AT, and keeps your markup standards‑compliant.
Sidebars typically contain tangential or ancillary content (e.g., related links, promos, author info). The ARIA role that matches that meaning is complementary. In HTML, the semantic element for the same concept is aside, which by default maps to the complementary landmark in accessibility APIs. Prefer native semantics first: use <aside> when possible. Only add role="complementary" when you can’t change the element type or when you need an explicit landmark for non-semantic containers.
How to fix:
- If the element is a sidebar: change
<div role="sidebar">to<aside>(preferred), or to<div role="complementary">. - Ensure each page has at most one primary
mainregion and that complementary regions are not essential to understanding the main content. - Provide an accessible name for the complementary region when multiple exist, using
aria-labeloraria-labelledby, to help users navigate landmarks.
Examples
Triggers the validator error
<divrole="sidebar">
<!-- Sidebar content -->
</div>
Fixed: use the semantic element (preferred)
<asidearia-label="Related articles">
<!-- Sidebar content -->
</aside>
Fixed: keep the container, apply a valid role
<divrole="complementary"aria-label="Related articles">
<!-- Sidebar content -->
</div>
Full document example with two sidebars (each labeled)
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Sidebar Landmarks Example</title>
</head>
<body>
<header>
<h1>News Today</h1>
</header>
<mainid="main">
<article>
<h2>Main Story</h2>
<p>...</p>
</article>
</main>
<asidearia-label="Trending topics">
<ul>
<li>Science</li>
<li>Politics</li>
<li>Sports</li>
</ul>
</aside>
<divrole="complementary"aria-labelledby="sponsor-title">
<h2id="sponsor-title">Sponsored</h2>
<p>Ad content</p>
</div>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
Notes:
- Do not invent ARIA roles (e.g.,
sidebar,hero,footer-nav). Use defined roles likecomplementary,navigation,banner,contentinfo, andmain. - Prefer native HTML elements (
aside,nav,header,footer,main) over generic containers with roles. - Label multiple complementary landmarks to make them distinguishable in screen reader landmark lists.
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>
<labelfor="address">Street Address</label>
<inputtype="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
<labelfor="address">Street Address</label>
<textareaid="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
<labelfor="address1">Address Line 1</label>
<inputtype="text"id="address1"name="address1"autocomplete="address-line1">
<labelfor="address2">Address Line 2</label>
<inputtype="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.
The article element has an implicit ARIA role of article, which signals to assistive technologies that it contains a self-contained, independently distributable piece of content—like a blog post, news story, or forum entry. When you add role="tabpanel" to an article, you're attempting to override this strong semantic meaning with a widget role, which the HTML specification does not permit. The ARIA in HTML specification defines a strict set of allowed roles for each HTML element, and tabpanel is not in the list of permissible roles for article.
This matters for several reasons. First, assistive technologies like screen readers rely on accurate role information to communicate the purpose of elements to users. An article element claiming to be a tabpanel creates a confusing and contradictory signal. Second, the W3C validator flags this as an error, meaning your markup is technically invalid. Third, browsers may handle this conflict inconsistently—some might honor the explicit role, while others might prioritize the element's native semantics, leading to unpredictable behavior across platforms.
The tabpanel role is designed for use in a tab interface pattern alongside role="tablist" and role="tab". According to the WAI-ARIA Authoring Practices, a tab panel should be a generic container that holds the content associated with a tab. Elements like div and section are ideal because they don't carry conflicting implicit roles (div has no implicit role, and section maps to region only when given an accessible name, which gets properly overridden by tabpanel).
To fix the issue, simply change the article element to a div or section. If you genuinely need the semantic meaning of article within a tab panel, nest the article inside the div that carries the tabpanel role.
Examples
Incorrect: tabpanel role on an article element
<articlerole="tabpanel"id="panel1">
<h2>Latest News</h2>
<p>Tab panel content here.</p>
</article>
This triggers the validation error because article does not allow the tabpanel role.
Correct: tabpanel role on a div
<divrole="tabpanel"id="panel1">
<h2>Latest News</h2>
<p>Tab panel content here.</p>
</div>
Correct: Nesting an article inside the tab panel
If you need the article semantics for the content within the panel, nest it:
<divrole="tabpanel"id="panel1">
<article>
<h2>Latest News</h2>
<p>This is a self-contained article displayed within a tab panel.</p>
</article>
</div>
Full tab interface example
Here's a complete, valid tab interface implementation:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Tab Interface Example</title>
</head>
<body>
<divrole="tablist"aria-label="Topics">
<buttonrole="tab"id="tab1"aria-controls="panel1"aria-selected="true">News</button>
<buttonrole="tab"id="tab2"aria-controls="panel2"aria-selected="false">Sports</button>
</div>
<divrole="tabpanel"id="panel1"aria-labelledby="tab1">
<p>Latest news content goes here.</p>
</div>
<divrole="tabpanel"id="panel2"aria-labelledby="tab2"hidden>
<p>Sports content goes here.</p>
</div>
</body>
</html>
Note the use of aria-controls on each tab to reference its corresponding panel, aria-labelledby on each tabpanel to reference its controlling tab, and the hidden attribute on inactive panels. These associations ensure assistive technologies can properly navigate the tab interface.
The W3C validator raises this error because ARIA roles must be compatible with the element they are applied to. A <ul> element has an implicit ARIA role of list, and overriding it with tabpanel creates a conflict. The tabpanel role signals to assistive technologies that the element is a panel of content activated by a corresponding tab. When this role is placed on a <ul>, screen readers lose the semantic meaning of the list (item count, list navigation, etc.) while also misrepresenting the element's function in the tab interface.
This matters for several reasons:
- Accessibility: Screen reader users rely on correct roles to navigate and understand page structure. A
<ul>marked astabpanelconfuses both its list semantics and its role in the tab interface. - Standards compliance: The ARIA in HTML specification defines which roles are allowed on which elements. The
tabpanelrole is not permitted on<ul>. - Browser behavior: Browsers may handle conflicting roles inconsistently, leading to unpredictable behavior across assistive technologies.
The fix is straightforward: wrap the <ul> inside a proper container element (like a <div> or <section>) and apply the tabpanel role to that container instead.
Examples
Incorrect: tabpanel role on a <ul>
This triggers the validation error because tabpanel is not a valid role for <ul>:
<divrole="tablist"aria-label="Recipe categories">
<buttonrole="tab"aria-controls="panel-1"aria-selected="true"id="tab-1">Appetizers</button>
<buttonrole="tab"aria-controls="panel-2"aria-selected="false"id="tab-2">Desserts</button>
</div>
<ulrole="tabpanel"id="panel-1"aria-labelledby="tab-1">
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
<ulrole="tabpanel"id="panel-2"aria-labelledby="tab-2"hidden>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
Correct: tabpanel role on a container wrapping the <ul>
Move the tabpanel role to a <div> and nest the <ul> inside it. This preserves both the tab panel semantics and the list semantics:
<divrole="tablist"aria-label="Recipe categories">
<buttonrole="tab"aria-controls="panel-1"aria-selected="true"id="tab-1">Appetizers</button>
<buttonrole="tab"aria-controls="panel-2"aria-selected="false"id="tab-2">Desserts</button>
</div>
<divrole="tabpanel"id="panel-1"aria-labelledby="tab-1">
<ul>
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
</div>
<divrole="tabpanel"id="panel-2"aria-labelledby="tab-2"hidden>
<ul>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
</div>
Correct: Using <section> as the tab panel
A <section> element also works well as a tab panel container, especially when the panel content is more complex:
<divrole="tablist"aria-label="Project info">
<buttonrole="tab"aria-controls="tasks-panel"aria-selected="true"id="tasks-tab">Tasks</button>
<buttonrole="tab"aria-controls="notes-panel"aria-selected="false"id="notes-tab">Notes</button>
</div>
<sectionrole="tabpanel"id="tasks-panel"aria-labelledby="tasks-tab">
<h2>Current tasks</h2>
<ul>
<li>Review pull requests</li>
<li>Update documentation</li>
</ul>
</section>
<sectionrole="tabpanel"id="notes-panel"aria-labelledby="notes-tab"hidden>
<h2>Meeting notes</h2>
<p>Discussed project timeline and milestones.</p>
</section>
In a properly structured tabbed interface:
- The
tablistrole goes on the container that holds the tab buttons. - Each tab trigger gets
role="tab"witharia-controlspointing to its panel'sid. - Each content panel gets
role="tabpanel"on a generic container like<div>or<section>, witharia-labelledbyreferencing the corresponding tab'sid. - List elements like
<ul>and<ol>should remain inside the panel as regular content, retaining their native list semantics.
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"
<labelfor="phone">Phone number</label>
<inputid="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
<labelfor="phone">Phone number (without country code)</label>
<inputid="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
<labelfor="phone">Phone number</label>
<inputid="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>
<labelfor="country-code">Country code</label>
<inputid="country-code"name="country-code"type="text"autocomplete="tel-country-code">
<labelfor="national">National number</label>
<inputid="national"name="national"type="text"autocomplete="tel-national">
</fieldset>
A URI (Uniform Resource Identifier) follows a strict syntax defined by RFC 3986. The general structure is scheme:scheme-data, where no spaces or other illegal characters are allowed between the colon and the scheme-specific data. When the W3C validator reports "Illegal character in scheme data," it means the parser found a character that isn't permitted in that position of the URI.
The most common cause of this error is adding a space after the colon in tel: links (e.g., tel: +123456789). While browsers may be forgiving and still handle the link, the markup is technically invalid. This matters for several reasons:
- Accessibility: Screen readers and assistive technologies rely on well-formed URIs to correctly identify link types. A malformed
tel:link might not be announced as a phone number. - Standards compliance: Invalid URIs violate the HTML specification, which requires the
hrefattribute to contain a valid URL. - Cross-device behavior: Mobile devices use the URI scheme to determine which app should handle a link. A malformed
tel:URI may fail to trigger the phone dialer on some devices or operating systems. - Interoperability: While some browsers silently trim spaces, others may encode them as
%20, potentially breaking the phone number or other scheme data.
To fix this issue, ensure there are no spaces or other illegal characters between the scheme's colon and the data that follows it. For telephone links specifically, the number should follow the colon directly, using only digits, hyphens, dots, parentheses, and the + prefix as defined by RFC 3966.
Examples
Incorrect: space after the colon in a tel: link
<ahref="tel: +1-234-567-8900">Call us</a>
The space between tel: and +1 is an illegal character in the URI scheme data.
Correct: no space after the colon
<ahref="tel:+1-234-567-8900">Call us</a>
Incorrect: space after the colon in a mailto: link
<ahref="mailto: support@example.com">Email support</a>
Correct: mailto: with no space
<ahref="mailto:support@example.com">Email support</a>
Incorrect: other illegal characters in scheme data
<ahref="tel:+1 234 567 8900">Call us</a>
Spaces within the phone number itself are also illegal characters in the URI. Use hyphens, dots, or no separators instead.
Correct: valid separators in a phone number
<ahref="tel:+1-234-567-8900">Call us</a>
<ahref="tel:+1.234.567.8900">Call us</a>
<ahref="tel:+12345678900">Call us</a>
Visual formatting vs. the href value
If you want the displayed phone number to include spaces for readability, format the visible text separately from the href value:
<ahref="tel:+12345678900">+1 234 567 8900</a>
The href contains a valid URI with no spaces, while the link text is formatted for easy reading. This gives you the best of both worlds — valid markup and a user-friendly display.
The autocomplete attribute does not accept "telephone" as a valid value — the correct value is "tel".
The autocomplete attribute helps browsers autofill form fields with previously saved user data. Each field type has a specific token defined in the HTML specification. For phone numbers, the valid token is "tel", not "telephone". Other related tokens include "tel-country-code", "tel-area-code", "tel-local", and "tel-extension" for more granular phone number parts.
Using the correct token ensures that browsers can properly suggest stored phone numbers to users, improving the form-filling experience.
Invalid Example
<labelfor="phone">Phone number</label>
<inputtype="tel"id="phone"name="phone"autocomplete="telephone">
Valid Example
<labelfor="phone">Phone number</label>
<inputtype="tel"id="phone"name="phone"autocomplete="tel">
A span element cannot have role="text" because text is not a valid WAI-ARIA role.
The role attribute accepts only values defined in the WAI-ARIA specification. Common valid roles include button, alert, status, img, presentation, and many others, but text is not among them.
The non-standard role="text" was sometimes used as a workaround to prevent screen readers like VoiceOver from splitting inline elements into separate announcements. For example, when a span wraps mixed text and inline elements, some screen readers read each child element as a separate item. Using role="text" forced them to treat the content as a single text run. However, this was never part of the ARIA spec and causes a validation error.
If the goal is to group inline content so screen readers announce it as one continuous phrase, use role="group" with an aria-label that provides the full text. Alternatively, if the span has no semantic purpose, remove the role attribute entirely.
HTML examples
Invalid usage
<p>
<spanrole="text">
Sale price: <strong>$49.99</strong>
</span>
</p>
Valid alternatives
Remove the role if it serves no accessibility purpose:
<p>
<span>
Sale price: <strong>$49.99</strong>
</span>
</p>
Or use role="group" with aria-label to ensure screen readers announce the content as a single phrase:
<p>
<spanrole="group"aria-label="Sale price: $49.99">
Sale price: <strong>$49.99</strong>
</span>
</p>
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
<metahttp-equiv="Content-Type"content="text/html; charset=iso-8859-15">
Valid
<metacharset="utf-8">
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