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.
This error is not about your HTML syntax being invalid — it's about a broken reference. The W3C Validator follows URLs it encounters in your markup (or a URL you submit directly for validation) and checks whether the server can actually deliver the resource. When the remote server returns an HTTP 404 (Not Found) response, the validator flags the issue because the referenced resource is missing or unreachable.
There are several common causes for this error:
- Typos in the URL — A misspelled filename, path, or domain name.
- Moved or deleted resources — The file existed at one point but has since been removed or relocated.
- Case sensitivity — Many web servers treat
Image.pngandimage.pngas different files. A mismatch in letter casing can produce a 404. - Incorrect relative paths — A relative URL that resolves differently than expected based on the document's location.
- External resources no longer available — Third-party CDNs or hosted files that have been taken down.
This matters because broken references degrade the user experience. Missing stylesheets can leave a page unstyled, missing scripts can break functionality, and missing images display broken image icons. Search engines also penalize pages with excessive broken links, and screen readers may announce confusing or unhelpful content when resources fail to load.
How to Fix It
- Check the URL carefully. Copy the full URL from your HTML, paste it into a browser, and see if it loads. If it returns a 404 page, the URL is wrong.
- Verify the file exists on the server. If you control the server, confirm the file is in the expected directory with the exact filename and extension.
- Fix case sensitivity issues. Ensure the capitalization in your URL matches the actual filename on the server.
- Update moved resources. If a file was relocated, update the
hreforsrcattribute to point to the new location. - Replace unavailable external resources. If a third-party resource is no longer available, find an alternative source, host a copy yourself, or remove the reference.
Examples
Broken image reference (triggers the error)
<img src="https://example.com/images/photo.jpeg" alt="A scenic landscape">
If photo.jpeg doesn't exist at that path (perhaps the actual file is named photo.jpg), the validator will report a 404 error.
Fixed image reference
<img src="https://example.com/images/photo.jpg" alt="A scenic landscape">
Broken stylesheet reference (triggers the error)
<link rel="stylesheet" href="/css/Styles.css">
If the file on the server is actually named styles.css (lowercase), a case-sensitive server will return a 404.
Fixed stylesheet reference
<link rel="stylesheet" href="/css/styles.css">
Broken script reference with incorrect path (triggers the error)
<script src="/assets/js/old-directory/app.js"></script>
If the script was moved to a different directory, this path no longer resolves.
Fixed script reference
<script src="/assets/js/app.js"></script>
Using a relative path incorrectly (triggers the error)
If your HTML file is at /pages/about.html and you reference an image like this:
<img src="images/logo.png" alt="Company logo">
The browser will look for /pages/images/logo.png. If the image actually lives at /images/logo.png, this will fail.
Fixed with a root-relative path
<img src="/images/logo.png" alt="Company logo">
The leading / ensures the path is resolved from the root of the site, regardless of where the HTML document is located.
The aria-describedby attribute is a core part of WAI-ARIA, the Web Accessibility Initiative's specification for making web content more accessible. It works by creating a relationship between an element and one or more other elements that provide additional descriptive text. Screen readers and other assistive technologies use this relationship to announce the descriptive text when a user interacts with the element.
When you set aria-describedby="some-id", the browser looks for an element with id="some-id" in the same document. If no matching element exists, the reference is broken. This means assistive technologies cannot find the description, and the attribute silently does nothing. The W3C validator flags this as an error because a dangling reference indicates a bug — either the referenced element was removed, renamed, or was never added.
This issue commonly arises due to:
- Typos in the
idvalue — thearia-describedbyvalue doesn't match the target element'sidexactly (the match is case-sensitive). - Dynamic content — the described-by element is rendered conditionally or injected by JavaScript after validation.
- Copy-paste errors — markup was copied from another page or component, but the referenced element wasn't included.
- Refactoring — an element's
idwas changed or the element was removed, but thearia-describedbyreference wasn't updated.
Multiple id values can be listed in aria-describedby, separated by spaces. Every single id in that list must resolve to an element in the document. If even one is missing, the validator will report an error for that reference.
How to fix it
- Check for typos. Compare the value in
aria-describedbyagainst theidof the target element. Remember thatidmatching is case-sensitive —helpTextandhelptextare different. - Add the missing element. If the descriptive element doesn't exist yet, create it with the matching
id. - Remove stale references. If the description is no longer needed, remove the
aria-describedbyattribute entirely rather than leaving a broken reference. - Verify all IDs in a multi-value list. If
aria-describedbycontains multiple space-separated IDs, confirm each one exists.
Examples
Broken reference (triggers the error)
In this example, aria-describedby points to password-help, but no element with that id exists in the document:
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-help">
Fixed by adding the referenced element
Adding an element with id="password-help" resolves the issue:
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-help">
<p id="password-help">Must be at least 8 characters with one number.</p>
Broken reference due to a typo
Here the aria-describedby value uses a different case than the element's id:
<input type="text" id="email" aria-describedby="emailHelp">
<small id="emailhelp">We'll never share your email.</small>
The fix is to make the id values match exactly:
<input type="text" id="email" aria-describedby="email-help">
<small id="email-help">We'll never share your email.</small>
Multiple IDs with one missing
When listing multiple descriptions, every id must be present:
<!-- "format-hint" exists but "length-hint" does not — this triggers the error -->
<input type="text" id="username" aria-describedby="format-hint length-hint">
<span id="format-hint">Letters and numbers only.</span>
Fix it by adding the missing element:
<input type="text" id="username" aria-describedby="format-hint length-hint">
<span id="format-hint">Letters and numbers only.</span>
<span id="length-hint">Between 3 and 20 characters.</span>
Removing the attribute when no description is needed
If the descriptive content has been removed and is no longer relevant, simply remove the aria-describedby attribute:
<input type="text" id="search">
The aria-controls attribute establishes a programmatic relationship between a controlling element (like a button, tab, or scrollbar) and the element it controls (like a panel, region, or content area). Assistive technologies such as screen readers use this relationship to help users navigate between related elements — for example, announcing that a button controls a specific panel and allowing the user to jump to it.
When the id referenced in aria-controls doesn't exist in the document, the relationship is broken. Screen readers may attempt to locate the target element and fail silently, or they may announce a control relationship that leads nowhere. This degrades the experience for users who rely on assistive technology and violates the WAI-ARIA specification, which requires that the value of aria-controls be a valid ID reference list pointing to elements in the same document.
Common causes of this error include:
- Typos in the
idor thearia-controlsvalue. - Dynamically generated content where the controlled element hasn't been rendered yet or has been removed from the DOM.
- Copy-paste errors where
aria-controlswas copied from another component but the correspondingidwas not updated. - Referencing elements in iframes or shadow DOM, which are considered separate document contexts.
The aria-controls attribute accepts one or more space-separated ID references. Every listed ID must match an element in the same document.
How to Fix
- Verify the target element exists in the document and has the exact
idthataria-controlsreferences. - Check for typos — ID matching is case-sensitive, so
mainPanelandmainpanelare not the same. - If the controlled element is added dynamically, ensure it is present in the DOM before or at the same time as the controlling element, or update
aria-controlsprogrammatically when the target becomes available. - If the controlled element is genuinely absent (e.g., conditionally rendered), remove the
aria-controlsattribute until the target element exists.
Examples
Incorrect: aria-controls references a non-existent ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="infopanel">
<p>Here is some additional information.</p>
</div>
This triggers the error because aria-controls="info-panel" does not match the actual id of "infopanel" (note the missing hyphen).
Correct: aria-controls matches an existing element's ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="info-panel">
<p>Here is some additional information.</p>
</div>
Correct: Tab and tab panel relationship
<div role="tablist">
<button role="tab" aria-controls="tab1-panel" aria-selected="true">
Overview
</button>
<button role="tab" aria-controls="tab2-panel" aria-selected="false">
Details
</button>
</div>
<div id="tab1-panel" role="tabpanel">
<p>Overview content goes here.</p>
</div>
<div id="tab2-panel" role="tabpanel" hidden>
<p>Details content goes here.</p>
</div>
Both aria-controls values — tab1-panel and tab2-panel — correctly correspond to elements present in the document.
Correct: Custom scrollbar controlling a region
<div role="scrollbar" aria-controls="main-content" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-orientation="vertical"></div>
<div id="main-content" role="region" aria-label="Main content">
<p>Scrollable content goes here.</p>
</div>
Correct: Controlling multiple elements
The aria-controls attribute can reference multiple IDs separated by spaces. Each ID must exist in the document.
<button aria-controls="section-a section-b">
Expand All Sections
</button>
<div id="section-a">
<p>Section A content.</p>
</div>
<div id="section-b">
<p>Section B content.</p>
</div>
The background CSS property accepts a variety of value types: named colors (red, blue), hexadecimal codes (#fff, #ff0000), color functions (rgb(), hsl(), rgba()), gradient functions (linear-gradient(), radial-gradient()), image URLs, and CSS keywords like none, transparent, or inherit. The word from is not among these valid values.
Why this happens
This error most commonly appears in one of these scenarios:
Legacy WebKit gradient syntax. Older versions of Safari and Chrome used a proprietary syntax:
-webkit-gradient(linear, left top, right top, from(#fff), to(#000)). Thefrom()andto()functions are part of this deprecated, non-standard format. If this syntax is used without the-webkit-prefix—or if the validator encounters it—the wordfromgets flagged as an invalid color value.Incorrectly written gradient shorthand. Some developers unfamiliar with the CSS gradient specification write something resembling natural language, like
background: from #fff to #000, which has no meaning in CSS.CSS
fromkeyword in relative color syntax. CSS Color Level 5 introduces relative color syntax using thefromkeyword (e.g.,rgb(from red r g b / 50%)). This is a newer feature that may not yet be recognized by the W3C CSS validator, which can lag behind the latest specifications. If you're using this syntax intentionally, the error may be a false positive from the validator, but be aware that browser support may still be limited.
Why it matters
Invalid CSS values are silently ignored by browsers, meaning your intended background styling won't be applied. The element will fall back to its default or inherited background, which can result in broken layouts, missing visual cues, or poor contrast that harms readability and accessibility. Using standard, valid CSS ensures consistent rendering across all browsers.
How to fix it
- Replace legacy
-webkit-gradient()syntax with the standardlinear-gradient()orradial-gradient()functions. - Use valid color formats for solid backgrounds: hex codes, named colors, or color functions.
- If using relative color syntax (
fromkeyword in CSS Color Level 5), understand that the validator may not yet support it. Consider adding a fallback value for broader compatibility.
Examples
Incorrect: legacy WebKit gradient syntax
The from() and to() functions in -webkit-gradient() are non-standard and will trigger this error if used as a background value:
<style>
.banner {
/* Non-standard syntax; "from" is not a valid CSS value */
background: -webkit-gradient(linear, left top, right top, from(#fff), to(#000));
}
</style>
<div class="banner">Legacy gradient</div>
Incorrect: made-up gradient shorthand
Writing gradient-like syntax without a proper CSS function is invalid:
<style>
.banner {
/* "from" and "to" are not valid CSS keywords here */
background: from #fff to #000;
}
</style>
<div class="banner">Invalid gradient</div>
Correct: standard linear gradient
Use linear-gradient() with a direction and comma-separated color stops:
<style>
.banner {
background: linear-gradient(to right, #fff, #000);
}
</style>
<div class="banner">Standard gradient</div>
Correct: solid color background
For a simple solid color, use any valid CSS color value:
<style>
.banner {
background: #fff;
}
</style>
<div class="banner">White background</div>
Correct: gradient with a fallback for older browsers
When using gradients, it's good practice to provide a solid color fallback:
<style>
.banner {
background: #fff;
background: linear-gradient(to bottom, #ffffff, #cccccc);
}
</style>
<div class="banner">Gradient with fallback</div>
Correct: relative color syntax with a fallback
If you intentionally use CSS Color Level 5 relative color syntax and the validator flags from, provide a fallback and be aware of current browser support:
<style>
.banner {
background: rgb(255, 0, 0);
background: rgb(from red r g b / 50%);
}
</style>
<div class="banner">Relative color with fallback</div>
Always verify that your background values use standard CSS syntax. When in doubt, test your styles in the W3C CSS Validator and check browser support on Can I Use.
The W3C HTML Validator doesn't just parse your markup—it also attempts to retrieve external resources referenced in elements like <link>, <script>, <img>, and <iframe>. When a server returns a 403 status code, it's telling the validator "you are not authorized to access this resource." The validator then reports this as an informational message because it cannot fully validate the referenced content.
Common Causes
Several server-side configurations can trigger this issue:
- Hotlink protection — Many servers and CDNs block requests that don't originate from an approved domain. Since the validator's requests come from
validator.w3.org, they get rejected. - IP-based restrictions or firewalls — The remote server may restrict access to specific IP ranges, blocking the validator's servers.
- User-Agent filtering — Some servers reject requests from bots or non-browser User-Agents, which includes the validator.
- Authentication requirements — The resource may sit behind a login wall or require API keys or tokens.
- Geographic restrictions — The server may use geo-blocking that prevents access from the validator's location.
Why This Matters
While this is typically an informational warning rather than a hard HTML error, it has practical implications:
- Incomplete validation — The validator can't check the referenced resource for errors. For example, if a CSS file returns 403, the validator can't verify the stylesheet for issues that might affect your page.
- Potential broken references — A 403 may indicate that the resource URL is incorrect or outdated, meaning real users could also experience issues depending on their browser or network configuration.
- Accessibility and reliability concerns — If the resource is intermittently blocked, some users may not receive critical stylesheets or scripts, leading to a degraded experience.
How to Fix It
- Verify the URL is correct — Double-check for typos or outdated paths.
- Host resources on your own server — Download the resource and serve it locally or from a CDN you control.
- Use a CDN that permits open access — Popular open CDNs like cdnjs, jsDelivr, or unpkg are designed to allow unrestricted access.
- Configure the remote server — If you control the server hosting the resource, whitelist the validator's User-Agent or allow requests from
validator.w3.org. - Remove unnecessary references — If the resource isn't actually needed, remove the element entirely.
Examples
Resource blocked by remote server
This references a stylesheet on a server that returns 403 to the validator:
<link rel="stylesheet" href="https://restricted-server.example.com/styles/main.css">
Fixed: Host the resource locally
Download the stylesheet and serve it from your own domain:
<link rel="stylesheet" href="/css/main.css">
Fixed: Use a publicly accessible CDN
Switch to a well-known open CDN that doesn't block external requests:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
Script blocked by hotlink protection
<script src="https://protected-site.example.com/js/library.min.js"></script>
Fixed: Use a reliable public source
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
Image blocked by access restrictions
<img src="https://private-cdn.example.com/photos/banner.jpg" alt="Welcome banner">
Fixed: Host the image yourself
<img src="/images/banner.jpg" alt="Welcome banner">
Keep in mind that this warning only appears during validation—it doesn't necessarily mean your end users are experiencing the same 403 error. Browsers send different headers (including cookies, referrers, and User-Agent strings) than the validator does, so the resource may load fine for visitors. However, it's still good practice to ensure your referenced resources are reliably accessible and that validation can complete fully.
The aria-labelledby attribute creates a relationship between an element and the text content that labels it. It works by pointing to the id of one or more elements whose text should be used as the accessible name. When the validator reports that aria-labelledby must point to an element in the same document, it means at least one of the id values you referenced doesn't correspond to any element on the page.
This typically happens for a few reasons:
- Typo in the
id— thearia-labelledbyvalue doesn't exactly match the target element'sid(remember, IDs are case-sensitive). - The referenced element was removed — the labeling element existed at some point but was deleted or moved, and the reference wasn't updated.
- The
idexists in a different document —aria-labelledbycannot reference elements across pages, iframes, or shadow DOM boundaries. The target must be in the same document. - Dynamic content not yet rendered — the element is inserted by JavaScript after the validator parses the static HTML.
This is primarily an accessibility problem. Screen readers and other assistive technologies rely on aria-labelledby to announce meaningful labels to users. When the reference is broken, the element effectively has no accessible name, which can make it impossible for users to understand its purpose. Browsers won't throw a visible error, so the issue can go unnoticed without validation or accessibility testing.
To fix the issue, verify that every id referenced in aria-labelledby exists in the same HTML document. Double-check spelling and casing. If you reference multiple IDs (space-separated), each one must resolve to an existing element.
Examples
Incorrect — referencing a non-existent id
The aria-labelledby attribute points to "dialog-title", but no element with that id exists:
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dlg-title">Confirm deletion</h2>
<p>Are you sure you want to delete this item?</p>
</div>
Correct — matching id values
Ensure the id in the referenced element matches exactly:
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm deletion</h2>
<p>Are you sure you want to delete this item?</p>
</div>
Incorrect — referencing multiple IDs where one is missing
When using multiple IDs, every one must be present. Here, "note-desc" doesn't exist:
<section aria-labelledby="note-heading note-desc">
<h3 id="note-heading">Important note</h3>
<p id="note-description">Please read carefully before proceeding.</p>
</section>
Correct — all referenced IDs exist
<section aria-labelledby="note-heading note-description">
<h3 id="note-heading">Important note</h3>
<p id="note-description">Please read carefully before proceeding.</p>
</section>
Incorrect — case mismatch
IDs are case-sensitive. "Main-Title" and "main-title" are not the same:
<nav aria-labelledby="Main-Title">
<h2 id="main-title">Site navigation</h2>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
Correct — consistent casing
<nav aria-labelledby="main-title">
<h2 id="main-title">Site navigation</h2>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
If you don't have a visible labeling element on the page and don't want to add one, consider using aria-label instead, which accepts a string value directly rather than referencing another element:
<nav aria-label="Site navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
A page must contain no more than one visible element with role="main" because this landmark role identifies the primary content area of the document, and having multiple instances creates ambiguity for assistive technologies.
Screen readers and other assistive tools use the main landmark to let users skip directly to the page's primary content. When two or more visible elements carry role="main", the tool cannot determine which one is the actual primary content block. The HTML <main> element has an implicit role="main", so mixing <main> elements with role="main" on other elements can also trigger this error.
A common cause is having multiple <main> elements without hiding the inactive ones. Single page applications sometimes keep several <main> elements in the DOM for different views. That is valid only if all but one are hidden using the hidden attribute.
Another cause is placing role="main" on a <div> while also having a <main> element elsewhere in the page.
Invalid example
<body>
<main>
<h1>Home</h1>
<p>Welcome to the home page.</p>
</main>
<main>
<h1>About</h1>
<p>About this site.</p>
</main>
</body>
Valid example
Only one <main> is visible at a time. The others use the hidden attribute:
<body>
<main>
<h1>Home</h1>
<p>Welcome to the home page.</p>
</main>
<main hidden>
<h1>About</h1>
<p>About this site.</p>
</main>
</body>
If you do not need multiple views, remove the extra <main> or role="main" entirely and keep a single one:
<body>
<header>
<h1>My Site</h1>
</header>
<main>
<p>Primary content goes here.</p>
</main>
<footer>
<p>Footer content.</p>
</footer>
</body>
A resource referenced in the HTML cannot be fetched because the server returned an HTTP 400 (Bad Request) status code.
This error appears when the W3C validator tries to retrieve a URL specified in your HTML and the remote server rejects the request. The validator follows URLs found in attributes like src, href, or action to check that referenced resources exist and are accessible. A 400 response means the server considered the request malformed or invalid.
Common causes include:
- A typo or malformed URL in an attribute value
- Unencoded special characters in the URL (spaces, angle brackets, curly braces)
- A URL that contains template placeholders like
{{variable}}or{id}that were never replaced with actual values - A URL pointing to a localhost or internal resource the validator cannot reach
- A URL scheme mismatch, such as using
http://when the server only acceptshttps://
The validator reports this as an informational warning rather than a strict HTML syntax error, but it signals that something about the URL is wrong or unreachable.
Example with the issue
<img src="https://example.com/images/photo one.jpg" alt="A photo">
<link rel="stylesheet" href="https://example.com/styles/main.css?v={{version}}">
The first URL has an unencoded space. The second contains a template placeholder that was never resolved, which the remote server rejects.
Fixed example
<img src="https://example.com/images/photo%20one.jpg" alt="A photo">
<link rel="stylesheet" href="https://example.com/styles/main.css?v=2.1.0">
Spaces are percent-encoded as %20, and the template placeholder is replaced with an actual value. If the URL points to an internal or private resource that the validator simply cannot access, the warning can be safely ignored, but verify the URL works in a browser first.
When you submit a URL to the W3C HTML Validator, the validator acts as an HTTP client: it sends a request to your server, downloads the HTML response, and then checks it for errors. A 504 Gateway Timeout status means the validator's request never received a timely response. The connection either timed out at your server, at an intermediary proxy or CDN, or somewhere along the network path.
This is fundamentally different from an HTML validation error. Your markup isn't being evaluated at all — the validator simply cannot reach it. Until the retrieval succeeds, no validation can take place.
Common Causes
Several things can prevent the validator from fetching your page:
- Slow server response — If your page takes a long time to generate (heavy database queries, unoptimized server-side code, resource-intensive CMS), the validator may time out before receiving a response.
- Firewall or WAF rules — A Web Application Firewall or security plugin may be blocking requests from the validator's IP addresses or user agent because they don't look like typical browser traffic.
- Geographic or IP-based restrictions — If your server restricts access by region or IP range, the validator (hosted in a different location) may be blocked.
- Private or local network — Sites running on
localhost, an intranet, or behind a VPN are not reachable from the public internet. - Reverse proxy or CDN misconfiguration — An intermediary like Nginx, Cloudflare, or a load balancer may be timing out while waiting for your origin server.
- Server is down or overloaded — The server may simply be unresponsive at the time of the request.
How to Fix It
1. Verify your site is publicly accessible
Open your URL in a browser from a different network (for example, using your phone's mobile data instead of your office Wi-Fi). If you can't reach it externally, the validator can't either.
2. Check server response time
Your page should respond within a few seconds. Use tools like curl to test response time from the command line:
curl -o /dev/null -s -w "HTTP Status: %{http_code}\nTime: %{time_total}s\n" https://example.com/your-page
If the response takes more than 10–15 seconds, consider optimizing your server-side code, enabling caching, or upgrading your hosting.
3. Check firewall and security rules
Review your server's firewall settings, .htaccess rules, or security plugin configuration. Ensure you're not blocking requests based on user agent strings or IP ranges that would affect the validator. The W3C Validator identifies itself with a specific User-Agent header — make sure it's not being rejected.
4. Use direct input as a workaround
If you cannot make your site publicly accessible (e.g., it's a staging environment), you can validate your HTML by pasting it directly into the validator instead of submitting a URL.
Go to https://validator.w3.org/#validate_by_input and paste your HTML source into the text area. This bypasses the network retrieval entirely.
Examples
Validating a page on a private network (will fail with 504)
Submitting a URL like the following to the W3C Validator will fail because the validator cannot reach private or local addresses:
http://192.168.1.50/index.html
http://localhost:3000/about.html
http://my-staging-site.internal/page.html
Using direct input instead (works)
Copy your HTML source and paste it into the validator's "Validate by Direct Input" tab:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>This content can be validated by pasting it directly.</p>
</body>
</html>
This approach lets you validate your markup regardless of server accessibility.
When to Contact Your Hosting Provider
If your site is intended to be publicly accessible but the validator consistently receives a 504 error, the issue likely lies with your server infrastructure. Contact your web hosting provider or server administrator and ask them to investigate:
- Whether a reverse proxy (Nginx, Apache, or a CDN) is timing out
- Whether any rate limiting or bot protection is blocking automated requests
- Whether the origin server is responding slowly under load
Once your server responds reliably to external HTTP requests, the W3C Validator will be able to fetch and validate your HTML normally.
An HTTP 202 Accepted status code indicates that the server has received and acknowledged the request, but the processing is not yet complete. This is commonly used for asynchronous operations — the server queues the work and responds immediately to let the client know the request was accepted without making the client wait. While this is perfectly valid for APIs and background job systems, it's not appropriate for serving HTML documents, stylesheets, scripts, or other resources that the browser (or validator) needs to read immediately.
The W3C HTML Validator works by fetching your page and any linked resources (CSS files, images, scripts, etc.) over HTTP. It expects a 200 OK response with the full content in the response body. When it receives a 202 Accepted, the body may be empty, incomplete, or contain a placeholder — none of which can be meaningfully validated. This results in the validator aborting its check for that resource and reporting the error.
Common Causes
- Server-side processing delays: Your web server or application framework is deferring content generation to a background process and returning
202as an interim response. - Asynchronous or queued endpoints: The URL points to an API-style endpoint that triggers a job rather than serving content directly.
- CDN or proxy misconfiguration: A content delivery network or reverse proxy in front of your server is returning
202while it fetches or generates the resource from the origin. - On-demand static site generation: Some platforms generate pages on first request and return
202until the build is complete (e.g., incremental static regeneration that hasn't cached the page yet).
How to Fix
The core fix is ensuring that the URL you submit to the validator responds with a 200 OK status and delivers the full resource content in the response body. Here are specific steps:
Check your server configuration. Make sure your web server or application returns
200 OKfor HTML pages and static assets. If background processing is needed, it should happen before the response is sent, or the result should be cached and served directly on subsequent requests.Avoid validating asynchronous endpoints. If a URL is designed to trigger background work (like a webhook or task queue), it's not a validatable HTML resource. Only submit URLs that serve complete HTML documents.
Pre-warm cached content. If your hosting platform uses on-demand generation, visit the URL in a browser first to trigger the build, then validate it once the page is fully generated and cached.
Inspect the response headers. Use browser developer tools or a command-line tool like
curl -I <url>to verify the status code your server actually returns. Look for theHTTP/1.1 202 Acceptedline and trace why it's being sent.
Examples
Incorrect server behavior (returns 202)
In this conceptual example, the server responds with 202 for an HTML page, which prevents the validator from checking it:
HTTP/1.1 202 Accepted
Content-Type: text/html
<!-- Content may be empty or incomplete -->
If you're using a Node.js/Express server, this kind of code would cause the problem:
app.get('/page', (req, res) => {
res.status(202).send('<p>Processing...</p>');
// Background work happens later
});
Correct server behavior (returns 200)
The server should respond with 200 OK and the complete HTML content:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page is fully rendered and ready to validate.</p>
</body>
</html>
The equivalent fix in a Node.js/Express server:
app.get('/page', (req, res) => {
const html = renderPage(); // Generate full content synchronously or await it
res.status(200).send(html);
});
Verifying the status code with curl
You can check what status code a URL returns before submitting it to the validator:
curl -I https://example.com/page
Look for HTTP/1.1 200 OK (or HTTP/2 200) in the first line of the output. If you see 202 Accepted, investigate your server configuration before attempting validation.
A resource linked in your HTML (such as a stylesheet, script, or image) returned a 500 Internal Server Error when the W3C validator tried to fetch it.
This is not an HTML syntax error — it's a server-side problem. The W3C validator attempts to retrieve external resources referenced in your document to perform additional checks. When it encounters a 500 status code, it means the remote server failed to process the request. This could be caused by a misconfigured server, a broken backend script, a temporarily unavailable resource, or a URL that only works with specific headers (like cookies or authentication) that the validator doesn't send.
Common culprits include <link> elements pointing to CSS files, <script> elements loading JavaScript, or even resources referenced in <img> or <source> tags.
To fix this, verify that the URL is correct and publicly accessible. Open it directly in your browser or test it with a tool like curl. If the resource is on your own server, check your server logs for the cause of the 500 error. If it's a third-party resource, consider hosting a local copy or using a CDN alternative.
HTML Examples
Before — referencing an unreachable resource
<link rel="stylesheet" href="https://example.com/broken-endpoint/styles.css">
<script src="https://example.com/broken-endpoint/app.js"></script>
After — using a valid, accessible URL
<link rel="stylesheet" href="https://example.com/css/styles.css">
<script src="https://example.com/js/app.js"></script>
If the resource is temporarily down and outside your control, the validator warning will resolve itself once the remote server is fixed. You can safely treat this as a warning rather than a blocking error in that case.
Understanding the Issue
In the early days of the Open Graph Protocol (used by Facebook, LinkedIn, and other platforms to parse shared links), developers were instructed to add XML namespace declarations like xmlns:og and xmlns:fb to the <html> element. This was borrowed from XHTML conventions where custom prefixed attributes required namespace bindings.
However, modern HTML5 documents are not XML. The HTML5 parser does not support arbitrary namespace declarations in the way XHTML/XML does. Attributes with colons in their local names (like xmlns:og) are not serializable as XML 1.0, meaning they cannot be reliably round-tripped between HTML and XML parsers. The W3C validator flags this because these attributes violate the HTML serialization rules.
The good news is that these namespace declarations have never been required for Open Graph tags to work. Social media crawlers (Facebook's, Twitter's, LinkedIn's, etc.) parse <meta> tags based on the property attribute value directly — they don't rely on XML namespace resolution. Browsers also ignore these attributes entirely, so removing them has zero impact on functionality.
How to Fix It
- Locate your
<html>opening tag. - Remove any
xmlns:og,xmlns:fb, or similar namespace attributes. - Keep your Open Graph
<meta>tags exactly as they are — they will continue to work.
Examples
❌ Incorrect: Namespace attributes on the <html> element
<!DOCTYPE html>
<html lang="en" xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<title>My Page</title>
<meta property="og:title" content="My Page Title">
<meta property="og:description" content="A description of my page.">
<meta property="og:image" content="https://example.com/image.jpg">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
This triggers the validator warning: Attribute with the local name "xmlns:og" is not serializable as XML 1.0 (and similarly for xmlns:fb).
✅ Correct: Clean <html> element without namespace attributes
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta property="og:title" content="My Page Title">
<meta property="og:description" content="A description of my page.">
<meta property="og:image" content="https://example.com/image.jpg">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The Open Graph <meta> tags remain unchanged and will continue to be recognized by Facebook, LinkedIn, and all other platforms that support the Open Graph Protocol. The only change is removing the unnecessary xmlns:og and xmlns:fb attributes from the <html> tag.
Common Variations
You may also encounter other namespace prefixes like xmlns:article, xmlns:product, or xmlns:music. These are all unnecessary in HTML5 and should be removed:
<!-- ❌ Remove all of these -->
<html lang="en" xmlns:og="http://ogp.me/ns#" xmlns:article="http://ogp.me/ns/article#" xmlns:fb="http://www.facebook.com/2008/fbml">
<!-- ✅ Keep it simple -->
<html lang="en">
Note that the standard xmlns attribute (without a colon prefix) is allowed on the <html> element in HTML5 if its value is exactly http://www.w3.org/1999/xhtml. However, even this is unnecessary and can be omitted. The prefixed forms like xmlns:og are never valid in HTML5.
Both <meta charset="UTF-8"> and <meta http-equiv="content-type" content="text/html; charset=UTF-8"> instruct the browser which character encoding to use when interpreting the document's bytes into text. Having both declarations in the same document creates a redundant and potentially conflicting situation. The HTML specification explicitly forbids including both, because if they ever specified different encodings, the browser would have to decide which one to trust, leading to unpredictable behavior.
Character encoding is critical for correctly displaying text. If the encoding is wrong or ambiguous, characters like accented letters, emoji, or symbols from non-Latin scripts can appear as garbled text (often called "mojibake"). By requiring a single, unambiguous declaration, the spec ensures browsers can reliably determine the encoding.
The <meta charset="UTF-8"> syntax was introduced with HTML5 as a shorter, cleaner alternative to the older <meta http-equiv="content-type"> approach. Both are valid on their own, but modern best practice strongly favors <meta charset="UTF-8"> for its simplicity. Whichever you choose, it should appear as early as possible within the <head> element — ideally as the first child — and must appear within the first 1024 bytes of the document so the browser can detect the encoding before parsing the rest of the content.
To fix this issue, search your document's <head> for both forms of the declaration and remove one of them. In most cases, you should keep <meta charset="UTF-8"> and remove the <meta http-equiv="content-type"> element.
Examples
Incorrect: both declarations present
This triggers the validation error because both methods of declaring the character encoding are used simultaneously.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: using <meta charset> (recommended)
This is the modern, preferred approach for HTML5 documents.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: using <meta http-equiv="content-type">
This older syntax is also valid on its own. You might encounter it in legacy codebases or when serving documents as application/xhtml+xml.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Common scenario: declarations split across includes
In templating systems or CMS platforms, the two declarations sometimes end up in different partial files — for example, one in a base layout and another injected by a plugin or theme. If you encounter this error unexpectedly, check all files that contribute to your <head> section, not just the main template.
<!-- base-layout.html -->
<head>
<meta charset="UTF-8">
<!-- ...other tags... -->
</head>
<!-- plugin-head-snippet.html (remove this duplicate) -->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
Audit your includes and partials to ensure only one character encoding declaration ends up in the final rendered <head>.
An <li> element inside an element with role="listbox" may only carry a role value of option or group.
The listbox role turns a list into a select-like widget, so assistive technologies expect every item in it to be a selectable option, optionally organized under a group. Any other role on the items, such as menuitem, button, or tab, contradicts the structure announced by the container, and screen readers can no longer present the widget coherently. This often happens in hand-rolled dropdowns built on <ul> and <li>, where the container got role="listbox" but the items kept a role from an earlier design.
If the items really are selectable choices, give them role="option". If they are something else entirely, change the container role instead of forcing the items. And when the widget is a plain single or multiple choice, a native <select> needs no ARIA at all.
Invalid example
The items use menuitem, which is not allowed inside a listbox:
<ul role="listbox" aria-label="Sort order">
<li role="menuitem">Newest first</li>
<li role="menuitem">Oldest first</li>
</ul>
Valid example
Each item becomes an option:
<ul role="listbox" aria-label="Sort order">
<li role="option" aria-selected="true">Newest first</li>
<li role="option" aria-selected="false">Oldest first</li>
</ul>
Or use a native control and drop the ARIA roles entirely:
<label>Sort order
<select>
<option selected>Newest first</option>
<option>Oldest first</option>
</select>
</label>
In CSS, most numeric values represent a length, time, angle, or other dimension, and the unit tells the browser how to interpret the number. Writing padding: 20 is ambiguous — does it mean 20 pixels, 20 ems, or 20 percent? Without a unit, the browser has no way to determine the author's intent, so it treats the value as invalid and discards the entire declaration. This can lead to broken layouts, missing spacing, or other visual problems that are difficult to debug.
The only exception to this rule is 0. Zero pixels is the same as zero ems, zero rems, or zero percent — it's always nothing regardless of the unit. Because of this, the CSS specification allows 0 to be written without a unit. In fact, omitting the unit on zero values is considered a best practice for cleaner, more concise CSS.
This validation error commonly appears in inline style attributes, which is where the W3C HTML Validator encounters it. However, the same rule applies to CSS written in <style> elements and external stylesheets.
Common causes of this error include:
- Accidentally forgetting to type the unit after a number.
- Copy-pasting values from design tools that don't include units.
- Confusing CSS with JavaScript's
element.style.width = "50"pattern (where some APIs accept unitless numbers). - Assuming that
pxis the default unit (it is not — there is no default unit in CSS).
Examples
Incorrect — missing units on numeric values
<div style="margin: 10; padding: 20; width: 300;">
Content here
</div>
The validator will report errors for 10, 20, and 300 because none of them have units.
Correct — units specified on all non-zero values
<div style="margin: 10px; padding: 20px; width: 300px;">
Content here
</div>
Correct — zero without a unit
<div style="margin: 0; padding: 0; border: 0;">
No units needed for zero
</div>
Correct — mixing zero and non-zero values
<div style="margin: 0 10px 0 20px;">
Top and bottom margins are zero, sides have units
</div>
Correct — using other unit types
<p style="font-size: 1.2em; line-height: 1.5em; margin-bottom: 2rem;">
Text with relative units
</p>
Note that line-height is a special case in CSS — it actually accepts a unitless number (like 1.5) as a valid value, where the number acts as a multiplier of the element's font size. This is not the same as a missing unit; it's a deliberately unitless ratio defined in the specification. Most other properties do not have this behavior.
The <base> element must appear before any <link> or <script> elements in the <head> section of the document.
The <base> element sets the base URL and/or default target for all relative URLs in a page. Because <link> and <script> elements often reference external resources using relative URLs, the browser needs to know the base URL before it encounters those elements. If <base> appears after a <link> or <script>, the browser may have already resolved their URLs without the intended base, leading to broken references or unexpected behavior.
Only one <base> element is allowed per document, and the HTML specification requires it to appear before any elements that have attributes accepting URLs. In practice, placing it as the first child of <head> (after <meta charset>) is the simplest way to satisfy this rule.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<link rel="stylesheet" href="styles/main.css">
<script src="js/app.js"></script>
<base href="https://example.com/">
</head>
<body>
<p>Hello</p>
</body>
</html>
The <base> element comes after the <link> and <script> elements, which triggers the validation error.
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="https://example.com/">
<title>Example</title>
<link rel="stylesheet" href="styles/main.css">
<script src="js/app.js"></script>
</head>
<body>
<p>Hello</p>
</body>
</html>
Moving <base> before the <link> and <script> elements fixes the error and ensures all relative URLs resolve correctly.
The sizes attribute and the srcset attribute are designed to work as a pair for responsive image delivery. The srcset attribute provides the browser with a list of image files and their intrinsic widths (e.g., 480w, 800w), while the sizes attribute tells the browser how much space the image will occupy in the layout at different viewport sizes. The browser combines this information to select the most appropriate image file to download.
When sizes appears without srcset, it serves no purpose. The browser has only the single image specified in the src attribute, so there's no decision to make about which image to load. The HTML specification explicitly requires that sizes must not be present unless srcset is also specified with width descriptors.
This error commonly occurs when a CMS or templating system outputs the sizes attribute by default, when srcset is accidentally removed during refactoring, or when developers copy markup snippets without including all the necessary attributes.
Beyond standards compliance, leaving orphaned sizes attributes creates confusing, harder-to-maintain code. Other developers (or your future self) may assume responsive images are configured when they aren't, leading to wasted debugging time.
How to fix it
You have two options:
- Add a
srcsetattribute if you want the browser to choose from multiple image sizes based on viewport width. Thesrcsetmust use width descriptors (w) forsizesto be meaningful. - Remove the
sizesattribute if you don't need responsive images and a singlesrcis sufficient.
Note that sizes is also valid on <source> elements inside a <picture> element — the same rule applies there. Every <source> with a sizes attribute must also have a srcset attribute.
Examples
❌ Incorrect: sizes without srcset
<img
src="image.jpg"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
The sizes attribute is present but there's no srcset, so the browser has no alternative images to pick from.
✅ Correct: sizes paired with srcset
<img
src="image-800w.jpg"
srcset="image-480w.jpg 480w, image-800w.jpg 800w"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
Here, srcset provides two images with their intrinsic widths. The sizes attribute tells the browser: "If the viewport is 600px or narrower, the image will display at 480px wide; otherwise, it will display at 800px wide." The browser uses this information to download the most efficient file.
✅ Correct: removing sizes when responsive images aren't needed
<img src="image.jpg" alt="A mountain landscape">
If a single image is sufficient, simply drop the sizes attribute.
❌ Incorrect: sizes on a <source> without srcset
<picture>
<source
media="(min-width: 800px)"
sizes="50vw">
<img src="fallback.jpg" alt="A sunset over the ocean">
</picture>
✅ Correct: sizes on a <source> with srcset
<picture>
<source
media="(min-width: 800px)"
srcset="wide-480w.jpg 480w, wide-960w.jpg 960w"
sizes="50vw">
<img src="fallback.jpg" alt="A sunset over the ocean">
</picture>
The <source> element now includes a srcset with width descriptors, giving the browser the candidate images it needs to make use of sizes.
A percentage value in your CSS exceeds the allowed range of 0 to 100.
CSS properties that accept percentage values — such as opacity, width, height, and others used in certain contexts — may be restricted to specific ranges. When you embed CSS in an HTML document (via the style attribute or a <style> element), the W3C HTML Validator checks these values and flags any that fall outside the permitted range.
A value like 100.01% is just slightly over the maximum of 100%. This is often a typo or a rounding error. While most browsers will silently clamp the value to 100%, it is still invalid and should be corrected.
HTML Examples
❌ Invalid: value out of range
<div style="width: 100.01%;">Content</div>
✅ Fixed: value within range
<div style="width: 100%;">Content</div>
HTML follows strict nesting rules: elements must be closed in the reverse order they were opened, like a stack. When the validator encounters </X> but the current open element is Y, it means something has gone wrong in the document structure. The browser's parser will attempt to recover from this mismatch, but the result may not reflect your intended layout or semantics.
There are several common causes for this error:
- Typos in tag names — for example, opening a
<div>but closing it with</dvi>. - Mismatched tags — opening one element but closing a different one, such as
<strong>closed with</em>. - Incorrect nesting order — overlapping elements where tags cross boundaries instead of being properly nested.
- Missing closing tags — a forgotten closing tag causes subsequent closing tags to be misaligned with the parser's expectation.
This matters because improperly nested HTML can cause unpredictable rendering across browsers, break CSS styling, interfere with JavaScript DOM manipulation, and create accessibility problems for screen readers that rely on a well-formed document tree.
To fix this error, trace back from the reported line to find where the mismatch originates. Ensure that every opening tag has a corresponding closing tag with the exact same name, and that elements are closed in the correct order (last opened, first closed).
Examples
Typo in the closing tag
<!-- ❌ Wrong: closing tag is misspelled -->
<section>
<p>Hello world</p>
</secton>
<!-- ✅ Fixed: closing tag matches the opening tag -->
<section>
<p>Hello world</p>
</section>
Mismatched tags
<!-- ❌ Wrong: <strong> is closed with </em> -->
<p>This is <strong>important</em> text.</p>
<!-- ✅ Fixed: closing tag matches the opening tag -->
<p>This is <strong>important</strong> text.</p>
Incorrectly nested (overlapping) elements
<!-- ❌ Wrong: <b> and <i> overlap each other -->
<p><b>Bold <i>and italic</b> text</i></p>
The validator sees </b> when the current open element is <i>. Elements must nest completely inside one another without overlapping.
<!-- ✅ Fixed: elements are properly nested -->
<p><b>Bold <i>and italic</i></b><i> text</i></p>
Missing closing tag causing a cascade of errors
<!-- ❌ Wrong: missing </div> for the inner div -->
<div class="outer">
<div class="inner">
<p>Content</p>
</div>
Here the single </div> closes inner, leaving outer unclosed. If more HTML follows, subsequent closing tags will be misaligned, potentially producing this error further down in the document.
<!-- ✅ Fixed: both divs are properly closed -->
<div class="outer">
<div class="inner">
<p>Content</p>
</div>
</div>
Tips for debugging
- Work from the first error — in HTML validation, one early mistake can cascade into many subsequent errors. Fix the first reported mismatch and re-validate before tackling later errors.
- Use consistent indentation — properly indented code makes it much easier to spot where nesting goes wrong.
- Use an editor with bracket/tag matching — most code editors can highlight matching opening and closing tags, making mismatches immediately visible.
In earlier versions of HTML (HTML 4 and XHTML), the type attribute was required on the <style> element to declare the MIME type of the styling language being used. The value was almost always text/css, as CSS has been the dominant stylesheet language for the web since its inception.
With HTML5, the specification changed. The type attribute on <style> now defaults to text/css, and since no browser supports any other styling language, the attribute serves no practical purpose. The WHATWG HTML Living Standard explicitly notes that the attribute is unnecessary and can be omitted. The W3C validator flags its presence as a warning to encourage cleaner, more modern markup.
Why This Matters
- Cleaner code: Removing unnecessary attributes reduces file size (even if marginally) and improves readability. Every attribute should earn its place in your markup.
- Standards compliance: Modern HTML encourages omitting default values when they add no information. Including
type="text/css"signals outdated coding practices. - Consistency: The same principle applies to
<script>elements, wheretype="text/javascript"is also unnecessary. Keeping your markup consistent by omitting both makes your codebase easier to maintain.
How to Fix It
The fix is straightforward: find every <style> element in your HTML that includes a type attribute and remove it. No other changes are needed — the browser behavior will be identical.
If you're working on a large codebase, a simple search for <style type= across your files will help you find all instances.
Examples
❌ Incorrect: Redundant type attribute
<style type="text/css">
p {
color: red;
}
</style>
<p>This text will be red.</p>
The type="text/css" attribute is unnecessary and triggers the W3C validator warning.
✅ Correct: type attribute omitted
<style>
p {
color: red;
}
</style>
<p>This text will be red.</p>
Without the type attribute, the browser still interprets the contents as CSS — the behavior is exactly the same.
❌ Incorrect: Other variations that also trigger the warning
The warning is triggered regardless of how the type value is formatted:
<style type="text/css" media="screen">
body {
font-family: sans-serif;
}
</style>
✅ Correct: Other attributes are fine, just remove type
<style media="screen">
body {
font-family: sans-serif;
}
</style>
Note that other valid attributes like media or nonce should be kept — only the type attribute needs to be removed.
The <script> element serves two distinct purposes in HTML: loading executable scripts and embedding non-executable data blocks. When the src attribute is present, the element is always being used to load an external script, so the type attribute must reflect a valid script type. Setting type to something like "text/html", "text/plain", or an invented value like "wrong" tells the browser this is not JavaScript, which means the external file referenced by src will be fetched but never executed — almost certainly not what the author intended.
The HTML specification restricts the allowed type values for <script src="..."> to three categories:
- An empty string (
type=""): Treated the same as the default, which is JavaScript. - A JavaScript MIME type: This includes
text/javascript,application/javascript, and other legacy JavaScript MIME types. Sincetext/javascriptis the default, specifying it is redundant. module: Indicates the script should be treated as a JavaScript module, enablingimport/exportsyntax and deferred execution by default.
Any value outside these categories — such as text/html, application/json, or a made-up string — is invalid when src is present.
Why this matters
Broken functionality: A non-JavaScript type on a <script> with src prevents the browser from executing the loaded file. The script is effectively dead code that still costs a network request.
Standards compliance: The HTML living standard explicitly forbids this combination. Validators flag it because it almost always indicates a mistake — either the wrong type was applied, or the src attribute was added by accident.
Maintainability: Future developers reading the code may be confused about whether the script is supposed to execute or serve as an inert data block. Keeping markup valid makes intent clear.
How to fix it
- Remove the
typeattribute entirely. This is the best approach for classic JavaScript. The default behavior istext/javascript, so notypeis needed. - Use
type="module"if the script uses ES module syntax (import/export). - If you intended a data block (e.g., embedding JSON or a template), remove the
srcattribute and place the content inline inside the<script>element instead. Data blocks with non-JavaScript types cannot usesrc.
Examples
Invalid: non-JavaScript types with src
These all trigger the validation error because the type value is not a JavaScript MIME type, an empty string, or "module":
<script type="text/html" src="template.html"></script>
<script type="application/json" src="data.json"></script>
<script type="text/plain" src="app.js"></script>
<script type="wrong" src="app.js"></script>
Valid: omitting the type attribute
The simplest and recommended fix for classic scripts — just drop type:
<script src="app.js"></script>
Valid: using a JavaScript MIME type
This is valid but redundant, since text/javascript is already the default. The validator may suggest omitting it:
<script type="text/javascript" src="app.js"></script>
Valid: using type="module"
Use this when the external script uses ES module syntax:
<script type="module" src="app.js"></script>
Valid: using an empty type attribute
An empty string is treated as the default. It's valid but unnecessary, and the validator may suggest removing it:
<script type="" src="app.js"></script>
Valid: data blocks without src
If you need a non-JavaScript type for an inline data block, remove the src attribute and place the content directly inside the element:
<script type="application/json" id="config">
{
"apiUrl": "https://example.com/api",
"debug": false
}
</script>
The X-UA-Compatible directive was introduced to control which rendering engine Internet Explorer would use when displaying a page. Setting it to a specific version like IE=10 locks the page into that version's rendering mode, even if the user has a newer version of Internet Explorer installed. This is problematic because it prevents the browser from using its most capable, standards-compliant rendering engine and can lead to unexpected layout or functionality issues.
The value IE=edge tells Internet Explorer to use the latest rendering engine available, which ensures the best possible standards compliance and feature support. The HTML specification and the W3C validator enforce this requirement because pinning to a specific IE version serves no forward-compatible purpose and can actively degrade the browsing experience.
It's worth noting that with Internet Explorer now being retired and replaced by Microsoft Edge, this meta tag is largely historical. However, if you include it at all, the validator requires that it be set to IE=edge. If your site no longer needs to support legacy versions of Internet Explorer, you can also simply remove the X-UA-Compatible declaration entirely — modern browsers ignore it.
How to fix it
- Find any
<meta http-equiv="X-UA-Compatible">tag in your HTML<head>. - Change the
contentattribute value from the specific version (e.g.,IE=10) toIE=edge. - If the directive is set as an HTTP response header on your server, update the header value there as well.
- Alternatively, remove the tag or header entirely if you no longer need IE compatibility.
Examples
Incorrect: pinned to a specific IE version
This triggers the validator error because IE=10 locks rendering to Internet Explorer 10 mode:
<meta http-equiv="X-UA-Compatible" content="IE=10">
Other version-specific values that would also trigger this error include IE=9, IE=11, IE=EmulateIE10, and similar variations.
Correct: using IE=edge
Replace the version-specific value with IE=edge:
<meta http-equiv="X-UA-Compatible" content="IE=edge">
Correct: full document example
When included in a complete HTML document, the X-UA-Compatible meta tag should appear early in the <head>, ideally right after the <meta charset> declaration:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Correct: removing the tag entirely
If IE support is no longer a concern, the simplest fix is to remove the meta tag altogether:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Server-side HTTP header
If the X-UA-Compatible value is being sent as an HTTP response header rather than a meta tag, update your server configuration. For example, in Apache:
# Incorrect
Header set X-UA-Compatible "IE=10"
# Correct
Header set X-UA-Compatible "IE=edge"
Or in Nginx:
# Incorrect
add_header X-UA-Compatible "IE=10";
# Correct
add_header X-UA-Compatible "IE=edge";
The @import rule allows you to pull in styles from another CSS file. According to the CSS specification, @import rules must precede all other at-rules (except @charset) and all style rules. When a browser encounters an @import after another valid CSS statement, it silently ignores the import. This means the styles you intended to load simply won't be applied, which can lead to broken layouts or missing designs that are difficult to debug.
The W3C validator catches this ordering problem and flags it because the misplaced @import will have no effect. Even though browsers won't throw a visible error, the imported stylesheet is completely discarded, making this a real functional issue rather than just a cosmetic validation warning.
The correct order inside a <style> element or CSS file is:
@charset(if needed, and only in external CSS files)@importrules- All other CSS rules (
@font-face,@media, style rules, etc.)
Examples
Incorrect: @import after a style rule
<style>
body {
margin: 0;
}
@import url("typography.css");
h1 {
color: navy;
}
</style>
The @import is placed after the body rule, so the browser will ignore it entirely. The styles from typography.css will never be loaded.
Incorrect: @import after another at-rule
<style>
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2");
}
@import url("reset.css");
</style>
Even though @font-face is an at-rule, it is not @charset or @import, so placing @import after it is invalid.
Correct: @import rules first
<style>
@import url("reset.css");
@import url("typography.css");
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2");
}
body {
margin: 0;
}
h1 {
color: navy;
}
</style>
All @import rules appear before any other statements, so they are valid and the imported stylesheets will be loaded correctly.
Correct: Multiple @import rules with @charset
In an external CSS file, you may also have a @charset declaration. It must come first, followed by @import rules:
@charset "UTF-8";
@import url("base.css");
@import url("components.css");
body {
font-family: sans-serif;
}
Alternatives to @import
While fixing the ordering resolves the validation error, it's worth noting that @import in CSS can cause performance issues because imported files are loaded sequentially rather than in parallel. Consider these alternatives:
- Multiple
<link>elements in your HTML<head>— these allow browsers to download stylesheets in parallel. - CSS bundling tools — build tools like Webpack, Vite, or PostCSS can combine multiple CSS files into one at build time, eliminating the need for
@importat runtime.
If you do use @import, just make sure every instance appears at the very top of your stylesheet, before any other CSS rules.
The async attribute is invalid on a <script> element whose type marks it as a data block, because browsers never fetch or execute data blocks.
When the type attribute is anything other than a JavaScript MIME type, module, importmap, or speculationrules, the browser treats the element as a data block: inline content that other scripts on the page can read, but that is never downloaded or run. The most common example is application/ld+json structured data. async controls when a script downloads and executes, so it has no meaning on an element that does neither.
This usually happens when a template or a performance plugin adds async to every <script> tag on the page, including the data blocks.
Examples
Invalid: async on a data block
<script type="application/ld+json" async>
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example"
}
</script>
Valid: data block without async
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example"
}
</script>
Remove the async attribute. If the script was actually meant to run, fix the type value instead: use a JavaScript MIME type, module, or omit the attribute entirely.
The defer attribute is invalid on a <script> element whose type marks it as a data block, because browsers never execute data blocks.
When the type attribute is anything other than a JavaScript MIME type, module, importmap, or speculationrules, the browser treats the element as a data block: inline content that other scripts on the page can read, but that is never fetched or run. The most common example is application/ld+json structured data. Since nothing executes, there is nothing to defer, and the attribute is invalid.
This usually happens when a template or a performance plugin adds defer to every <script> tag on the page, including the data blocks.
Examples
Invalid: defer on a data block
<script type="application/ld+json" defer>
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example"
}
</script>
Valid: data block without defer
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example"
}
</script>
Remove the defer attribute. If the script was actually meant to run, fix the type value instead: use a JavaScript MIME type, module, or omit the attribute entirely.
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