HTML Guides for link
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 <link> element defines a relationship between the current document and an external resource — most commonly stylesheets, icons, preloaded assets, or canonical URLs. According to the HTML specification, the element must include at least one of the href or imagesrcset attributes so the browser knows what resource is being linked. A <link> element without either attribute is essentially an empty declaration: it tells the browser about a relationship type (via rel) but provides no actual resource to fetch or reference.
This validation error commonly occurs in a few scenarios:
- Templating or CMS issues: A dynamic template generates a <link> tag but the URL variable is empty or undefined, resulting in a bare element with no href.
- Incomplete code: A developer adds a <link> with a rel attribute intending to fill in the href later but forgets to do so.
- Copy-paste mistakes: Attributes are accidentally removed during editing or refactoring.
Fixing this is important for several reasons. Browsers may ignore the element entirely or behave unpredictably when encountering a <link> with no resource URL. Unnecessary elements without purpose add bloat to the document and can confuse other developers reading the code. Additionally, tools that parse HTML — such as search engine crawlers and assistive technologies — rely on well-formed markup to correctly understand document relationships.
To resolve the issue, add an href attribute pointing to the target resource, use an imagesrcset attribute when providing responsive image sources, or include both when appropriate.
Examples
Invalid: missing both href and imagesrcset
This <link> declares a stylesheet relationship but doesn’t specify where the stylesheet is located:
<link rel="stylesheet">
This preload link has an as attribute but no resource to fetch:
<link rel="preload" as="image" type="image/png">
Fixed: using href
The most common fix is adding an href attribute with a valid URL:
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="canonical" href="https://example.com/page">
Fixed: using imagesrcset
For responsive image preloading, the imagesrcset attribute specifies multiple image sources at different resolutions. This is valid without href:
<link rel="preload" as="image" type="image/png" imagesrcset="icon-1x.png 1x, icon-2x.png 2x">
Fixed: using both href and imagesrcset
You can combine both attributes. The href serves as a fallback resource while imagesrcset provides responsive alternatives:
<link rel="preload" as="image" href="icon-1x.png" imagesrcset="icon-1x.png 1x, icon-2x.png 2x">
Handling dynamic templates
If your <link> elements are generated dynamically, make sure the element is only rendered when a valid URL is available. For example, in a template, wrap the output in a conditional check rather than outputting an empty <link>:
<!-- Bad: outputs a link even when the URL is empty -->
<link rel="stylesheet" href="">
<!-- Good: only include the element when there's a real URL -->
<link rel="stylesheet" href="styles.css">
Note that href="" resolves to the current page URL and is technically valid syntax (it won’t trigger this specific error), but it’s almost certainly not what you intend. Always ensure the href value points to the correct resource.
The HTML specification restricts where <link> elements can appear based on their purpose. Links that load resources needed for rendering (like stylesheets and preloaded assets) or carry microdata (itemprop) are allowed in <body> because they have a direct relationship to the content around them. Other types of <link> elements—canonical URLs, icons, alternate versions—are document-level metadata and belong exclusively in <head>.
This matters for several reasons. Browsers may ignore or inconsistently handle <link> elements placed in unexpected locations, leading to issues like missing canonical signals for search engines or broken favicon references. Standards compliance also ensures your HTML is forward-compatible and behaves predictably across all browsers.
Common causes
Direct placement in <body>
The most straightforward cause is placing a metadata <link> directly inside <body>, often due to a CMS, template system, or plugin injecting it in the wrong location.
Implicit <body> creation by the parser
A subtler cause occurs when an element that’s only valid in <body> appears inside <head>. When the HTML parser encounters such an element (like <img>, <div>, or <p>), it implicitly closes the <head> and opens the <body>. Any <link> elements that follow are then treated as descendants of <body>, even though they appear to be inside <head> in your source code.
For example, an <img> tag in the <head> causes the parser to switch to body context, so the subsequent <link rel="canonical"> is interpreted as being inside <body> and triggers this error.
How to fix it
-
Move disallowed <link> elements to <head>: If a <link> with rel="canonical", rel="icon", rel="alternate", or similar values is in <body>, move it into <head>.
-
Check for body-only elements in <head>: Look for elements like <img>, <div>, <p>, <script> (without src), or text content that may have been accidentally placed in <head>. These cause the parser to implicitly close <head>, making everything after them part of <body>.
-
Use allowed rel values if a body placement is intentional: If you genuinely need a <link> in <body>, ensure it uses one of the permitted rel values (stylesheet, preload, prefetch, preconnect, dns-prefetch, modulepreload, pingback, prerender) or has an itemprop attribute.
Examples
❌ <link rel="canonical"> placed in <body>
<body>
<link rel="canonical" href="https://example.com/page">
<h1>Welcome</h1>
</body>
✅ Move it to <head>
<head>
<title>My Page</title>
<link rel="canonical" href="https://example.com/page">
</head>
<body>
<h1>Welcome</h1>
</body>
❌ An <img> in <head> forces implicit body context
Even though the <link> appears to be in <head>, the <img> causes the parser to switch to body context:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<img src="photo.jpg" alt="A smiling cat">
<link rel="canonical" href="https://example.com/">
</head>
<body>
<p>Some content</p>
</body>
</html>
✅ Move the <img> to <body> where it belongs
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<link rel="canonical" href="https://example.com/">
</head>
<body>
<img src="photo.jpg" alt="A smiling cat">
<p>Some content</p>
</body>
</html>
✅ Allowed <link> elements inside <body>
These are valid because they use permitted rel values:
<body>
<article>
<link rel="stylesheet" href="article-theme.css">
<h2>Article Title</h2>
<p>Content here.</p>
</article>
<link rel="prefetch" href="/next-page.html">
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
</body>
✅ Using itemprop for microdata
A <link> with an itemprop attribute is also valid inside <body>:
<body>
<div itemscope itemtype="https://schema.org/Product">
<span itemprop="name">Widget</span>
<link itemprop="availability" href="https://schema.org/InStock">
</div>
</body>
The preload value of the <link> element’s rel attribute lets you declare fetch requests in the HTML <head>, telling the browser to start loading critical resources early in the page lifecycle—before the main rendering machinery kicks in. This can significantly improve performance by ensuring key assets are available sooner and are less likely to block rendering.
However, a preload hint is incomplete without the as attribute. The as attribute tells the browser what kind of resource is being fetched. This matters for several important reasons:
- Request prioritization: Browsers assign different priorities to different resource types. A stylesheet is typically higher priority than an image. Without as, the browser cannot apply the correct priority, and the preloaded resource may be fetched with a low default priority, undermining the purpose of preloading.
- Content Security Policy (CSP): CSP rules are applied based on resource type (e.g., script-src, style-src). Without knowing the type, the browser cannot enforce the appropriate policy.
- Correct Accept header: The as value determines which Accept header the browser sends with the request. For example, an image request sends a different Accept header than a script request. An incorrect or missing header could lead to unexpected responses from the server.
- Cache matching: When the resource is later requested by a <script>, <link rel="stylesheet">, or other element, the browser needs to match it against the preloaded resource in its cache. Without as, the browser may not recognize the preloaded resource and could fetch it again, resulting in a duplicate request—the opposite of what you intended.
The HTML specification explicitly requires the as attribute when rel="preload" is used, making this a conformance error.
Common as Values
The as attribute accepts a specific set of values. Here are the most commonly used ones:
| Value | Resource Type |
|---|---|
| script | JavaScript files |
| style | CSS stylesheets |
| font | Web fonts |
| image | Images |
| fetch | Resources fetched via fetch() or XMLHttpRequest |
| document | HTML documents (for <iframe>) |
| audio | Audio files |
| video | Video files |
| track | WebVTT subtitle tracks |
| worker | Web workers or shared workers |
Examples
Incorrect: Missing as attribute
This will trigger the validation error because the browser doesn’t know what type of resource is being preloaded:
<link rel="preload" href="/fonts/roboto.woff2">
<link rel="preload" href="/js/app.js">
Correct: as attribute included
Adding the appropriate as value tells the browser exactly what kind of resource to expect:
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/images/hero.webp" as="image">
Note on fonts and crossorigin
When preloading fonts, you must also include the crossorigin attribute, even if the font is hosted on the same origin. This is because font fetches are CORS requests by default. Without crossorigin, the preloaded font won’t match the later font request and will be fetched twice:
<!-- Correct: includes both as and crossorigin for fonts -->
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Preload Example</title>
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<h1>Hello, world!</h1>
<script src="/js/app.js"></script>
</body>
</html>
Choosing the correct as value is straightforward—just match it to how the resource will ultimately be used on the page. If you’re preloading a stylesheet, use as="style"; if it’s a script, use as="script", and so on.
The as attribute specifies the type of content a <link> element is fetching — such as "style", "script", "font", or "image". The browser uses this information to set the correct request headers, apply the right Content Security Policy, and assign the appropriate priority to the request. However, the HTML specification restricts the as attribute to <link> elements whose rel attribute is either "preload" or "modulepreload". Using as with any other rel value (like "stylesheet", "icon", or a missing rel altogether) is invalid HTML.
This validation error commonly occurs in two scenarios:
- You intended to preload a resource but forgot to set rel="preload" — for example, writing <link href="styles.css" as="style"> without specifying rel.
- You added as to a regular <link> by mistake — for example, writing <link rel="stylesheet" href="styles.css" as="style">, where as is unnecessary because rel="stylesheet" already tells the browser what type of resource it is.
Getting this right matters for several reasons. Browsers rely on valid rel values to determine how to handle linked resources. An incorrect combination may cause the browser to ignore the as attribute entirely, leading to double-fetching of resources or incorrect prioritization. Additionally, invalid HTML can cause unpredictable behavior across different browsers.
How to fix it
- If you want to preload a resource (font, stylesheet, image, script, etc.), set rel="preload" and keep the as attribute.
- If you want to preload a JavaScript module, set rel="modulepreload". The as attribute defaults to "script" for module preloads and is optional in that case.
- If you’re using a different rel value like "stylesheet" or "icon", remove the as attribute — it’s not needed and not allowed.
Examples
Incorrect: as attribute without rel="preload"
This <link> has as="style" but no rel attribute:
<link href="styles.css" as="style">
Incorrect: as attribute with rel="stylesheet"
The as attribute is not valid on a stylesheet link:
<link rel="stylesheet" href="styles.css" as="style">
Correct: preloading a stylesheet
Use rel="preload" with the as attribute to hint the resource type:
<link rel="preload" href="styles.css" as="style">
Note that preloading a stylesheet doesn’t apply it — you still need a separate <link rel="stylesheet"> to actually use the CSS.
Correct: preloading a font
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
The crossorigin attribute is required when preloading fonts, even if they’re served from the same origin.
Correct: preloading a JavaScript module
<link rel="modulepreload" href="app.js">
With rel="modulepreload", the as attribute defaults to "script", so you can omit it. You may still include it explicitly if you prefer:
<link rel="modulepreload" href="app.js" as="script">
Correct: regular stylesheet (no as needed)
If you simply want to load a stylesheet, no as attribute is required:
<link rel="stylesheet" href="styles.css">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Preload Example</title>
<!-- Preload critical resources -->
<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="hero.jpg" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Apply the stylesheet -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
<img src="hero.jpg" alt="Hero banner">
</body>
</html>
The hreflang attribute on a link element tells browsers and search engines the language and optional region of the linked resource. Its value must be a valid BCP 47 language tag, which follows a specific structure: a primary language subtag (like en, fr, de) optionally followed by a region subtag (like US, GB, FR). The region subtag must be a valid two-letter ISO 3166-1 alpha-2 country code.
The tag en-EN is invalid because EN is not a recognized country code. There is no country with the code “EN” — it’s a common mistake where the language code is simply repeated as the region. This pattern shows up frequently with other languages too, such as fr-FR (which happens to be valid because FR is the country code for France) leading people to assume en-EN follows the same logic. However, EN is not assigned to any country in ISO 3166-1, so the validator correctly rejects it.
Why This Matters
- SEO and internationalization: Search engines like Google use hreflang values to serve the correct localized version of a page. An invalid value may cause search engines to ignore the tag entirely, undermining your localization strategy.
- Standards compliance: Browsers and tools rely on well-formed language tags to apply correct locale-specific behavior such as font selection, date formatting hints, and spell-checking language.
- Accessibility: Screen readers and assistive technologies may use language information to select the correct speech synthesis voice. Invalid language tags can lead to content being read in the wrong accent or language.
Common Invalid Region Subtags
This mistake isn’t limited to English. Here are some commonly seen invalid patterns and their corrections:
| Invalid | Why It’s Wrong | Valid Alternatives |
|---|---|---|
| en-EN | EN is not a country code | en, en-US, en-GB, en-AU |
| de-DE | ✅ Actually valid — DE is Germany | de, de-DE, de-AT, de-CH |
| ja-JA | JA is not a country code | ja, ja-JP |
| zh-ZH | ZH is not a country code | zh, zh-CN, zh-TW |
| ko-KO | KO is not a country code | ko, ko-KR |
The key takeaway: don’t assume you can double the language code to make a region subtag. Always verify against the ISO 3166-1 alpha-2 country code list.
How to Fix It
- If you don’t need a regional variant, just use the bare language subtag: en.
- If you need a specific regional variant, pair the language subtag with the correct country code: en-US for American English, en-GB for British English, en-AU for Australian English, etc.
- Look up the country code if you’re unsure. The region subtag corresponds to a country, not a language.
Examples
Incorrect — Invalid region subtag
<link rel="alternate" href="https://example.com/en/" hreflang="en-EN">
The region subtag EN does not correspond to any country and will trigger a validation error.
Correct — Language only
If the content is simply in English without a specific regional variant, omit the region:
<link rel="alternate" href="https://example.com/en/" hreflang="en">
Correct — Language with valid region subtags
When you need to differentiate between regional variants, use proper country codes:
<link rel="alternate" href="https://example.com/en-us/" hreflang="en-US">
<link rel="alternate" href="https://example.com/en-gb/" hreflang="en-GB">
<link rel="alternate" href="https://example.com/en-au/" hreflang="en-AU">
Correct — Full set of hreflang links with x-default
A typical multilingual setup with properly formed language tags:
<link rel="alternate" href="https://example.com/" hreflang="x-default">
<link rel="alternate" href="https://example.com/en/" hreflang="en">
<link rel="alternate" href="https://example.com/en-us/" hreflang="en-US">
<link rel="alternate" href="https://example.com/fr/" hreflang="fr">
<link rel="alternate" href="https://example.com/de/" hreflang="de">
<link rel="alternate" href="https://example.com/ja/" hreflang="ja">
Note the use of x-default to indicate the default or language-selection page — this is a special value recognized by search engines for fallback purposes.
MIME types (also called media types) follow a strict type/subtype structure as defined by IETF standards. For example, image/png has the type image and the subtype png. The value "favicon" doesn’t follow this format — it has no slash and no subtype — so the validator reports “Subtype missing.” This is a common mistake that happens when developers confuse the purpose of the icon (a favicon) with the format of the file (an image in a specific encoding).
While most browsers are forgiving and will still display the favicon even with an invalid type value, using incorrect MIME types can cause issues. Some browsers or tools may ignore the <link> element entirely if the type doesn’t match a recognized format. It also hurts standards compliance and makes your HTML less predictable across different environments.
The correct MIME type depends on the file format of your favicon:
- .ico files: image/x-icon (or image/vnd.microsoft.icon)
- .png files: image/png
- .svg files: image/svg+xml
- .gif files: image/gif
It’s also worth noting that the type attribute on <link rel="icon"> is entirely optional. If omitted, the browser will determine the file type from the server’s Content-Type header or by inspecting the file itself. Removing it is a perfectly valid fix.
Examples
❌ Invalid: using “favicon” as the type
<link rel="icon" href="/favicon.png" type="favicon">
The value "favicon" is not a valid MIME type, triggering the validation error.
✅ Fixed: using the correct MIME type for a PNG favicon
<link rel="icon" href="/favicon.png" type="image/png">
✅ Fixed: using the correct MIME type for an ICO favicon
<link rel="icon" href="/favicon.ico" type="image/x-icon">
✅ Fixed: using the correct MIME type for an SVG favicon
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
✅ Fixed: omitting the type attribute entirely
<link rel="icon" href="/favicon.png">
Since the type attribute is optional for <link rel="icon">, removing it avoids the error and lets the browser detect the format automatically. This is often the simplest and most maintainable approach.
URLs follow strict syntax rules defined by RFC 3986. Within the path segment of a URL, only a specific set of characters is allowed: unreserved characters (letters, digits, -, ., _, ~), percent-encoded characters (like %20), and certain reserved sub-delimiters. When the W3C validator encounters a character outside this allowed set in a <link> element’s href attribute, it flags the error.
Common causes of this issue include:
- Template placeholders left in the URL, such as {{variable}} or ${path}, where curly braces and dollar signs haven’t been resolved or encoded.
- Spaces in file paths, such as href="styles/my file.css" instead of using %20 or renaming the file.
- Copy-paste errors that introduce invisible or special Unicode characters.
- Backslashes (\) used instead of forward slashes (/), which is a common mistake on Windows systems.
- Unencoded query-like characters placed in the path portion of the URL.
This matters because browsers may interpret malformed URLs inconsistently. A URL that works in one browser might fail in another. Additionally, invalid URLs can break resource loading, cause accessibility issues when assistive technologies try to process the document, and lead to unexpected behavior with proxies, CDNs, or other intermediaries that strictly parse URLs.
To fix the issue, inspect the href value reported in the error and either:
- Remove the illegal character if it was included by mistake.
- Percent-encode the character if it must be part of the URL (e.g., a space becomes %20, a pipe | becomes %7C).
- Rename the referenced file or directory to avoid special characters altogether (the simplest and most reliable approach).
Examples
Incorrect: Space in the path
<link rel="stylesheet" href="styles/my styles.css">
The space character is not allowed in a URL path segment. The validator will flag this as an illegal character.
Fixed: Percent-encode the space
<link rel="stylesheet" href="styles/my%20styles.css">
Better fix: Rename the file to avoid spaces
<link rel="stylesheet" href="styles/my-styles.css">
Incorrect: Template placeholder left unresolved
<link rel="stylesheet" href="styles/{{theme}}/main.css">
Curly braces { and } are not valid in URL path segments. This commonly happens with server-side or client-side templating syntax that wasn’t processed before the HTML was served.
Fixed: Use a valid resolved path
<link rel="stylesheet" href="styles/dark/main.css">
Incorrect: Backslash used as path separator
<link rel="stylesheet" href="styles\main.css">
Backslashes are not valid URL characters. URLs always use forward slashes.
Fixed: Use forward slashes
<link rel="stylesheet" href="styles/main.css">
Incorrect: Pipe character in the URL
<link rel="stylesheet" href="styles/font|icon.css">
Fixed: Percent-encode the pipe character
<link rel="stylesheet" href="styles/font%7Cicon.css">
Full valid document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Webpage</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="icon" href="images/favicon.ico">
</head>
<body>
<h1>Welcome to my webpage!</h1>
<p>Here is some content.</p>
</body>
</html>
When in doubt, run your URL through a URL encoder or validator separately to confirm all characters are legal. As a general best practice, stick to lowercase letters, digits, hyphens, and forward slashes in your file and directory names—this avoids encoding issues entirely and makes your URLs clean and predictable.
The <link> element is used to define relationships between the current document and external resources — most commonly stylesheets, icons, and preloaded assets. The href attribute specifies the URL of that external resource, and it is the core purpose of the element. An empty href attribute makes the <link> element meaningless because there is no resource to fetch or reference.
Why This Is a Problem
Standards compliance: The HTML specification requires the href attribute on <link> to be a valid, non-empty URL. An empty string does not qualify as a valid URL, so the validator flags it as an error.
Unexpected browser behavior: When a browser encounters an empty href, it may resolve it relative to the current document’s URL. This means the browser could end up making an unnecessary HTTP request for the current page itself, interpreting the HTML response as a stylesheet or other resource. This wastes bandwidth, can slow down page loading, and may trigger unexpected rendering issues.
Accessibility and semantics: An empty href provides no useful information to browsers, screen readers, or other user agents about the relationship between the document and an external resource. It adds noise to the DOM without contributing anything functional.
How to Fix It
- Provide a valid URL: If the <link> element is meant to reference a resource, set href to the correct URL of that resource.
- Remove the element: If no resource is needed, remove the entire <link> element rather than leaving it with an empty href.
- Check dynamic rendering: This issue often occurs when a templating engine or CMS outputs a <link> element with a variable that resolves to an empty string. Add a conditional check so the element is only rendered when a valid URL is available.
Examples
❌ Incorrect: Empty href attribute
<link rel="stylesheet" href="">
This triggers the validation error because href is empty.
❌ Incorrect: Empty href from a template
<!-- A template variable resolved to an empty string -->
<link rel="icon" type="image/png" href="">
✅ Correct: Valid href pointing to a resource
<link rel="stylesheet" href="/css/main.css">
✅ Correct: Valid href for a favicon
<link rel="icon" type="image/png" href="/images/favicon.png">
✅ Correct: Remove the element if no resource is needed
<!-- Simply omit the <link> element entirely -->
✅ Correct: Conditional rendering in a template
If you’re using a templating language, wrap the <link> in a conditional so it only renders when a URL is available. For example, in a Jinja2-style template:
{% if stylesheet_url %}
<link rel="stylesheet" href="{{ stylesheet_url }}">
{% endif %}
This ensures the <link> element is never output with an empty href.
The imagesizes attribute is used exclusively on <link> elements that have rel="preload" and as="image". It works in tandem with the imagesrcset attribute to allow the browser to preload the most appropriate image from a set of candidates — mirroring how sizes and srcset work on an <img> element. When the browser encounters these attributes on a <link>, it can begin fetching the right image resource early, before it even parses the <img> tag in the document body.
When imagesizes is set to an empty string (""), the browser has no information about the intended display size of the image, which defeats the purpose of responsive image preloading. The browser cannot select the best candidate from imagesrcset without knowing how large the image will be rendered. An empty value is invalid per the HTML specification, which requires the attribute to contain a valid source size list (the same syntax used by the sizes attribute on <img>).
This matters for both performance and standards compliance. Responsive preloading is a performance optimization — an empty imagesizes undermines that by leaving the browser unable to make an informed choice. From a standards perspective, the validator correctly rejects the empty value because the attribute’s defined value space does not include the empty string.
How to fix it
- Provide a valid sizes value that matches the sizes attribute on the corresponding <img> element in your page. This tells the browser how wide the image will be at various viewport widths.
- Remove imagesizes entirely if you don’t need responsive preloading. If you’re preloading a single image (using href instead of imagesrcset), you don’t need imagesizes at all.
Examples
❌ Bad: empty imagesizes attribute
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="">
The empty imagesizes="" triggers the validation error and prevents the browser from selecting the correct image candidate.
✅ Fixed: providing a valid sizes value
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px">
The imagesizes value uses the same syntax as the sizes attribute on <img>. It provides media conditions paired with lengths, with a fallback length at the end. This value should match the sizes attribute on the corresponding <img> element in your markup.
✅ Fixed: simple full-width image
<link
rel="preload"
as="image"
imagesrcset="banner-640.jpg 640w, banner-1280.jpg 1280w"
imagesizes="100vw">
If the image spans the full viewport width, 100vw is a straightforward and valid value.
✅ Fixed: removing the attribute when not needed
<link rel="preload" as="image" href="logo.png">
If you’re preloading a single, non-responsive image, omit both imagesrcset and imagesizes and use the href attribute instead. The imagesizes attribute is only meaningful when paired with imagesrcset.
The imagesrcset attribute is used exclusively on <link> elements that have rel="preload" and as="image". It mirrors the srcset attribute of the <img> element, allowing the browser to preload the most appropriate image resource based on the current viewport and display conditions. When the validator encounters imagesrcset="" (an empty value), it reports this error because an empty string is not a valid source set — it must contain at least one image candidate string.
Each image candidate string in the imagesrcset value consists of a URL followed by an optional width descriptor (e.g., 480w) or pixel density descriptor (e.g., 2x). Multiple candidates are separated by commas. This is the same syntax used by the srcset attribute on <img> elements.
This issue typically arises when a CMS, static site generator, or templating engine outputs the imagesrcset attribute with an empty value — for example, when a responsive image field has no data. Browsers may ignore the malformed attribute, but it results in invalid HTML, can cause unexpected preloading behavior, and signals that the page’s resource hints are misconfigured. Fixing it ensures standards compliance and that the browser’s preload scanner works as intended.
How to fix it
- Provide a valid source set — populate imagesrcset with one or more image candidate strings.
- Remove the attribute — if you don’t have multiple image sources to preload, remove imagesrcset (and imagesizes) from the <link> element entirely. You can still preload a single image using just the href attribute.
- Conditionally render — if your templating system might produce an empty value, add logic to omit the attribute when no responsive sources are available.
When using imagesrcset, you should also include the imagesizes attribute (mirroring the sizes attribute on <img>) so the browser can select the correct candidate based on layout information.
Examples
❌ Empty imagesrcset triggers the error
<link rel="preload" as="image" href="hero.jpg" imagesrcset="" imagesizes="">
The empty imagesrcset value is invalid and produces the W3C validation error.
✅ Valid imagesrcset with width descriptors
<link
rel="preload"
as="image"
href="hero-800.jpg"
imagesrcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
This tells the browser to preload the most appropriate image based on the viewport width, matching the responsive behavior of the corresponding <img> element on the page.
✅ Valid imagesrcset with pixel density descriptors
<link
rel="preload"
as="image"
href="logo.png"
imagesrcset="logo.png 1x, logo@2x.png 2x">
This preloads the correct logo variant based on the device’s pixel density.
✅ Removing the attribute when no responsive sources exist
<link rel="preload" as="image" href="hero.jpg">
If you only have a single image to preload, simply use href without imagesrcset. This is valid and avoids the error entirely.
✅ Conditional rendering in a template
If you’re using a templating language, conditionally include the attribute:
<!-- Pseudocode example -->
<link
rel="preload"
as="image"
href="hero.jpg"
{% if responsive_sources %}
imagesrcset="{{ responsive_sources }}"
imagesizes="{{ image_sizes }}"
{% endif %}>
This prevents the attribute from being rendered with an empty value when no responsive image data is available.
The fetchpriority attribute is a hint to the browser about the relative priority of fetching a particular resource compared to other resources of the same type. It is defined in the WHATWG HTML living standard as an enumerated attribute with exactly three valid values:
- high — The resource should be fetched at a higher priority relative to other resources of the same type.
- low — The resource should be fetched at a lower priority relative to other resources of the same type.
- auto — The browser determines the appropriate priority (this is the default behavior).
Values like "highest", "critical", "urgent", or any other string outside these three are not recognized. When the W3C validator encounters an invalid value, it reports: Bad value “highest” for attribute “fetchpriority” on element “link”.
Why This Matters
Standards compliance: Browsers treat unrecognized fetchpriority values as equivalent to "auto", meaning your intended priority hint is silently ignored. If you wrote fetchpriority="highest" expecting it to be even more urgent than "high", that extra emphasis has no effect — the browser simply falls back to its default prioritization.
Developer intent: An invalid value masks your real intention. Another developer reading the code might assume "highest" does something special, when in reality the browser discards it. Using the correct value makes the code’s purpose clear and ensures the hint is actually applied.
Accessibility and performance: The fetchpriority attribute is commonly used on <link rel="preload"> elements for critical resources like fonts, stylesheets, or hero images. If your priority hint is silently ignored due to an invalid value, key resources may not load as quickly as intended, potentially degrading the user experience.
How to Fix It
Replace the invalid value with one of the three accepted values. In most cases, if you used "highest", you likely meant "high":
- Find every <link> element where fetchpriority has an invalid value.
- Change the value to "high", "low", or "auto".
- If you’re unsure which priority to use, omit the attribute entirely — the browser will use "auto" by default.
Note that fetchpriority also works on <img>, <script>, and <iframe> elements, and the same three-value restriction applies to all of them.
Examples
Incorrect: Invalid fetchpriority value
<link rel="preload" href="hero.webp" as="image" fetchpriority="highest">
The value "highest" is not valid. The browser ignores the hint and falls back to default prioritization.
Correct: Using "high" for elevated priority
<link rel="preload" href="hero.webp" as="image" fetchpriority="high">
Correct: Using "low" for deferred resources
<link rel="preload" href="analytics.js" as="script" fetchpriority="low">
Correct: Omitting the attribute for default behavior
<link rel="preload" href="style.css" as="style">
When omitted, the browser uses its default priority logic, which is equivalent to fetchpriority="auto".
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fetch Priority Example</title>
<link rel="preload" href="hero.webp" as="image" fetchpriority="high">
<link rel="preload" href="fonts/body.woff2" as="font" type="font/woff2" crossorigin fetchpriority="high">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img src="hero.webp" alt="Hero banner" fetchpriority="high">
<p>Page content goes here.</p>
</body>
</html>
This example preloads a hero image and a font with fetchpriority="high", and also applies the hint to the <img> element itself — all using valid attribute values.
URLs used in HTML attributes must follow the URL Living Standard, which defines a specific set of characters that are allowed in each part of a URL. The query component of a URL — everything after the ? — permits most printable ASCII characters, but certain characters are still considered illegal and must be percent-encoded. When the W3C validator encounters one of these forbidden characters in the href of a <link> element, it raises this error.
Common characters that trigger this issue include:
| Character | Percent-encoded |
|---|
| | (pipe) | %7C | | [ (left bracket) | %5B | | ] (right bracket) | %5D | | { (left brace) | %7B | | } (right brace) | %7D | | ^ (caret) | %5E | | ` (backtick) | %60 | | (space) | %20 |
Why this matters
While many modern browsers are lenient and will silently fix malformed URLs, relying on this behavior is risky. Invalid URLs can cause problems in several ways:
- Inconsistent browser behavior: Not all user agents handle illegal characters the same way, which can lead to broken stylesheets or resources failing to load.
- Interoperability issues: Proxies, CDNs, and other intermediaries may reject or mangle URLs with illegal characters.
- Standards compliance: Valid HTML requires valid URLs in attributes. An illegal character in the href makes the entire document non-conforming.
- Copy-paste and sharing reliability: Malformed URLs are more likely to break when shared across systems, emails, or documentation.
How to fix it
Identify the illegal characters in your URL’s query string and replace each one with its percent-encoded equivalent. If you’re generating URLs programmatically, use a proper URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python) to ensure all special characters are encoded correctly.
Examples
❌ Illegal pipe character in the query string
This is a common pattern seen with Google Fonts URLs that use | to separate font families:
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto">
✅ Pipe character percent-encoded
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans%7CRoboto">
❌ Square brackets in the query string
Some APIs or frameworks use bracket notation in query parameters:
<link rel="stylesheet" href="https://example.com/styles?themes[]=dark&themes[]=compact">
✅ Square brackets percent-encoded
<link rel="stylesheet" href="https://example.com/styles?themes%5B%5D=dark&themes%5B%5D=compact">
❌ Space character in the query string
<link rel="stylesheet" href="https://example.com/css?file=my styles.css">
✅ Space character percent-encoded
<link rel="stylesheet" href="https://example.com/css?file=my%20styles.css">
Note that for Google Fonts specifically, the modern API (v2) uses a different URL format that avoids the pipe character altogether. Where possible, consider updating to the latest version of an API rather than just encoding the old URL.
The device-width and device-height media features (along with their min- and max- prefixed variants) originally referred to the physical dimensions of the device’s entire screen, regardless of how much space was actually available to the document. In practice, this distinction caused confusion and inconsistent behavior across browsers. On most modern devices and browsers, device-width and width return the same value anyway, making the device-* variants redundant.
The device-* media features were frequently used as a proxy to detect mobile devices, but this was never a reliable approach. A narrow browser window on a desktop monitor would not trigger a max-device-width query even though the available layout space was small. Conversely, modern phones and tablets with high-resolution screens can report large device widths that don’t reflect the actual CSS viewport size. The viewport-based alternatives (width, height, aspect-ratio) more accurately represent the space available for rendering your content.
Using deprecated media features causes W3C validation warnings and may eventually lose browser support entirely. Replacing them ensures your stylesheets are future-proof, standards-compliant, and behave consistently across all devices and window sizes.
How to fix it
Replace any device-width, device-height, or device-aspect-ratio media feature (including min- and max- prefixed versions) with the corresponding viewport-based equivalent:
| Deprecated feature | Replacement |
|---|---|
| device-width | width |
| min-device-width | min-width |
| max-device-width | max-width |
| device-height | height |
| min-device-height | min-height |
| max-device-height | max-height |
| device-aspect-ratio | aspect-ratio |
The width media feature describes the width of the viewport (the targeted display area of the output device), including the size of a rendered scroll bar if any. This is the value you almost always want when writing responsive styles.
Examples
Incorrect: using deprecated max-device-width
This triggers the validation error because max-device-width is deprecated:
<link rel="stylesheet" media="only screen and (max-device-width: 768px)" href="mobile.css">
Correct: using max-width instead
Replace max-device-width with max-width to query the viewport width:
<link rel="stylesheet" media="only screen and (max-width: 768px)" href="mobile.css">
Incorrect: using deprecated min-device-width in a range
<link rel="stylesheet" media="screen and (min-device-width: 768px) and (max-device-width: 1024px)" href="tablet.css">
Correct: using viewport-based equivalents
<link rel="stylesheet" media="screen and (min-width: 768px) and (max-width: 1024px)" href="tablet.css">
Incorrect: using deprecated device-aspect-ratio
<link rel="stylesheet" media="screen and (device-aspect-ratio: 16/9)" href="widescreen.css">
Correct: using aspect-ratio
<link rel="stylesheet" media="screen and (aspect-ratio: 16/9)" href="widescreen.css">
Applying the same fix in CSS @media rules
The same deprecation applies to @media rules inside stylesheets. While the W3C validator specifically flags the media attribute on <link> elements, you should update your CSS as well:
/* Deprecated */
@media only screen and (max-device-width: 768px) {
.sidebar { display: none; }
}
/* Correct */
@media only screen and (max-width: 768px) {
.sidebar { display: none; }
}
If your site relies on a <meta name="viewport"> tag (as most responsive sites do), the viewport width already matches the device’s CSS pixel width, so switching from device-width to width will produce identical results in virtually all cases.
The device-width, device-height, and device-aspect-ratio media features (including their min- and max- prefixed variants) refer to the physical dimensions of the entire screen, not the available space where your content is actually rendered. This distinction matters because the viewport size — the area your page occupies — can differ significantly from the full device screen size. For example, a browser window might not be maximized, or browser chrome might consume screen real estate. Using device-based queries means your responsive breakpoints may not match what users actually see.
These features were also commonly used as a proxy for detecting mobile devices, but this approach is unreliable. A small-screened laptop and a large tablet can have similar device widths, making device-based detection a poor heuristic. The W3C deprecated these features and recommends using viewport-based alternatives that more accurately reflect the rendering context of your document.
Beyond standards compliance, using deprecated media features can cause issues with future browser support. While current browsers still recognize them, there is no guarantee they will continue to do so. Replacing them now ensures your stylesheets remain functional and forward-compatible.
How to fix it
Replace each deprecated device-* media feature with its viewport-based equivalent:
| Deprecated feature | Replacement |
|---|---|
| device-width | width |
| min-device-width | min-width |
| max-device-width | max-width |
| device-height | height |
| min-device-height | min-height |
| max-device-height | max-height |
| device-aspect-ratio | aspect-ratio |
| min-device-aspect-ratio | min-aspect-ratio |
| max-device-aspect-ratio | max-aspect-ratio |
The width media feature describes the width of the targeted display area of the output device. For continuous media, this is the width of the viewport including the size of a rendered scroll bar (if any). This is almost always the value you actually want when building responsive layouts.
Examples
Incorrect: using deprecated min-device-width
This triggers the validation error because min-device-width is deprecated:
<link rel="stylesheet" media="only screen and (min-device-width: 768px)" href="tablet.css">
Correct: using min-width instead
Replace min-device-width with min-width to query the viewport width:
<link rel="stylesheet" media="only screen and (min-width: 768px)" href="tablet.css">
Incorrect: using max-device-width for a mobile breakpoint
<link rel="stylesheet" media="screen and (max-device-width: 480px)" href="mobile.css">
Correct: using max-width
<link rel="stylesheet" media="screen and (max-width: 480px)" href="mobile.css">
Incorrect: using device-aspect-ratio
<link rel="stylesheet" media="screen and (device-aspect-ratio: 16/9)" href="widescreen.css">
Correct: using aspect-ratio
<link rel="stylesheet" media="screen and (aspect-ratio: 16/9)" href="widescreen.css">
Incorrect: combining multiple deprecated features
<link rel="stylesheet" media="screen and (min-device-width: 768px) and (max-device-width: 1024px)" href="tablet.css">
Correct: using viewport-based equivalents
<link rel="stylesheet" media="screen and (min-width: 768px) and (max-width: 1024px)" href="tablet.css">
The same replacements apply when these deprecated features appear in CSS @media rules or in the media attribute on <style> and <source> elements. Updating them across your entire codebase ensures consistent, standards-compliant responsive behavior.
The media attribute tells the browser under which conditions a linked stylesheet (or other resource) should be applied. Its value must be a valid media query list as defined by the CSS Media Queries specification. When the W3C HTML Validator reports a parse error for this attribute, it means the value couldn’t be parsed as a valid media query. The browser may ignore the attribute entirely or behave unpredictably, potentially loading the stylesheet in all conditions or not at all.
Common causes of this error include:
- Typos in media types or features — e.g., scrren instead of screen, or max-widht instead of max-width.
- Missing parentheses around media features — e.g., max-width: 600px instead of (max-width: 600px).
- Invalid or stray characters — e.g., extra commas, semicolons, or unmatched parentheses.
- Using CSS syntax instead of media query syntax — e.g., writing @media screen or including curly braces.
- Incorrect logical operators — e.g., using or at the top level instead of a comma, or misspelling not or only.
- Server-side template placeholders left unresolved — e.g., media="{{mediaQuery}}" rendered literally.
Getting this right matters for standards compliance and predictable cross-browser behavior. Browsers are generally lenient and may try to recover from malformed media queries, but the recovery behavior isn’t guaranteed to be consistent. A broken media attribute can cause stylesheets to load when they shouldn’t (wasting bandwidth) or not load when they should (breaking layout). It also affects performance, since browsers use the media attribute to prioritize resource loading.
To fix the issue, verify that your media value is a syntactically correct media query. Use valid media types (screen, print, all), wrap media features in parentheses, and separate multiple queries with commas.
Examples
Incorrect: missing parentheses around media feature
<!-- ❌ Parse error: media features must be wrapped in parentheses -->
<link rel="stylesheet" href="responsive.css" media="max-width: 600px">
Correct: parentheses around media feature
<link rel="stylesheet" href="responsive.css" media="(max-width: 600px)">
Incorrect: typo in media type
<!-- ❌ Parse error: "scrren" is not a valid media type -->
<link rel="stylesheet" href="styles.css" media="scrren">
Correct: valid media type
<link rel="stylesheet" href="styles.css" media="screen">
Incorrect: using or instead of a comma to separate queries
<!-- ❌ Parse error: top-level "or" is not valid between media queries -->
<link rel="stylesheet" href="styles.css" media="screen or print">
Correct: comma-separated media query list
<link rel="stylesheet" href="styles.css" media="screen, print">
Incorrect: stray semicolon in the value
<!-- ❌ Parse error: semicolons are not valid in media queries -->
<link rel="stylesheet" href="styles.css" media="screen; (max-width: 768px)">
Correct: combining media type and feature with and
<link rel="stylesheet" href="styles.css" media="screen and (max-width: 768px)">
More valid media query examples
<!-- Simple media type -->
<link rel="stylesheet" href="print.css" media="print">
<!-- Apply to all media (default behavior, but explicit) -->
<link rel="stylesheet" href="base.css" media="all">
<!-- Multiple conditions with "and" -->
<link rel="stylesheet" href="tablet.css" media="screen and (min-width: 768px) and (max-width: 1024px)">
<!-- Using "not" to exclude a media type -->
<link rel="stylesheet" href="no-print.css" media="not print">
<!-- Multiple queries separated by commas (logical OR) -->
<link rel="stylesheet" href="special.css" media="(max-width: 600px), (orientation: portrait)">
<!-- Prefers-color-scheme for dark mode -->
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
If you’re unsure whether your media query is valid, try testing it in a browser’s developer tools by adding a <style> block with @media and the same query — if the browser highlights it as invalid, the same value will fail in the media attribute.
The media attribute on a <link> element specifies which media or device types the linked resource (typically a stylesheet) is designed for. It accepts a valid media query or a comma-separated list of media types. In the current CSS specification (Media Queries Level 4), only three media types remain valid: all, screen, and print. All other media types from older specifications — including projection, handheld, tv, tty, braille, embossed, and aural — have been deprecated.
The projection media type was originally intended to target presentation and projector-based displays. In practice, browser support for it was extremely limited (only Opera had meaningful support), and the use case never gained traction. The CSS Working Group deprecated it because the distinction between a “screen” and a “projection” display is no longer meaningful — modern browsers treat projectors, external displays, and monitors uniformly under the screen type.
Why this matters
- Standards compliance: Using deprecated media types causes W3C validation errors, which can signal broader code quality issues.
- No practical effect: Modern browsers simply ignore unrecognized media types. If projection is the only value in your media attribute, the stylesheet may not load at all in some browsers. If it appears alongside screen, the browser loads the stylesheet based on the screen match and silently discards projection — meaning the deprecated value adds nothing.
- Maintainability: Keeping deprecated values in your code can confuse other developers and create the false impression that the stylesheet has special behavior for projectors.
How to fix it
- Remove projection from the media attribute value.
- If screen or another valid type was already listed alongside projection, keep the valid type.
- If projection was the only value, replace it with screen (since projectors are treated as screens by modern browsers).
- If the stylesheet should apply universally, remove the media attribute entirely or set it to all.
Examples
Incorrect
Using the deprecated projection media type alongside screen:
<link rel="stylesheet" href="style.css" media="screen, projection">
Using projection as the sole media type:
<link rel="stylesheet" href="slides.css" media="projection">
Correct
Remove projection and keep the valid screen type:
<link rel="stylesheet" href="style.css" media="screen">
Replace projection with screen, since projectors are handled as screens:
<link rel="stylesheet" href="slides.css" media="screen">
Target both screens and print:
<link rel="stylesheet" href="style.css" media="screen, print">
If the stylesheet should apply to all devices, omit the media attribute (which defaults to all):
<link rel="stylesheet" href="style.css">
Or explicitly set it to all:
<link rel="stylesheet" href="style.css" media="all">
Using media queries instead of media types
If you need more granular control over when a stylesheet applies — for example, targeting large displays commonly used for presentations — you can use a media query with feature conditions instead of relying on deprecated media types:
<link rel="stylesheet" href="presentation.css" media="screen and (min-width: 1920px)">
This approach is standards-compliant and gives you far more precise targeting than the old media types ever provided.
The media attribute on a <link> element specifies the conditions under which the linked resource should apply. It accepts either a simple media type or a full media query. When the validator reports “unrecognized media,” it means the value you provided doesn’t match any known media type or valid media query syntax.
Several older media types that were defined in earlier CSS specifications have been deprecated. Types like handheld, projection, tv, tty, aural, braille, and embossed are no longer recognized as valid. Modern CSS and HTML only support three media types: all, screen, and print. If you’re using a deprecated type, you should replace it with an appropriate modern media query that targets the device characteristics you need.
Beyond deprecated types, this error also occurs when a media query expression is malformed — for example, missing parentheses around a feature expression, using an unknown feature name, or having a typo in the value.
Why this matters
- Standards compliance: Using unrecognized media types means your HTML doesn’t conform to the current HTML and CSS specifications.
- Browser behavior: Browsers may ignore the entire <link> element or apply the resource unconditionally when they encounter an unrecognized media type, leading to unexpected results.
- Performance: The media attribute helps browsers prioritize resource loading. A valid media query allows the browser to defer loading stylesheets that don’t match the current context (e.g., print stylesheets), improving page load performance.
How to fix it
- Replace deprecated media types with screen, print, or all, or use modern media queries that target specific device features.
- Check for typos in your media type or query expression.
- Validate your media query syntax — feature expressions must be wrapped in parentheses and use recognized feature names like max-width, orientation, or prefers-color-scheme.
Examples
Incorrect: using a deprecated media type
<link rel="stylesheet" href="mobile.css" media="handheld">
The handheld media type is deprecated and will trigger the validation error.
Incorrect: misspelled media type
<link rel="stylesheet" href="styles.css" media="screen">
Incorrect: malformed media query
<link rel="stylesheet" href="responsive.css" media="max-width: 768px">
The feature expression is missing its surrounding parentheses.
Correct: using valid media types
<link rel="stylesheet" href="general.css">
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="screen.css" media="screen">
When no media attribute is specified, it defaults to all.
Correct: replacing deprecated types with modern media queries
Instead of media="handheld", use a media query that targets small screens or specific device capabilities:
<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 768px)">
Correct: using complex media queries
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="portrait.css" media="screen and (orientation: portrait)">
<link rel="stylesheet" href="large.css" media="screen and (min-width: 1200px)">
Valid media types reference
| Media type | Description |
|---|---|
| all | Matches all devices (default when omitted) |
| Matches printers and print preview mode | |
| screen | Matches screens (computers, tablets, phones) |
For anything more specific than these three types, use media feature expressions like (max-width: 600px), (hover: hover), or (prefers-reduced-motion: reduce) to target the exact device characteristics you need.
The type attribute on a <link> element specifies the MIME type of the linked resource. MIME types follow a specific format: a type and subtype separated by a single forward slash, like text/css, image/png, or application/json. They never contain the :// sequence found in URLs.
This error most commonly occurs when a URL is accidentally placed in the type attribute instead of in the href attribute, or when the attributes are confused with one another. For example, writing type="https://example.com/style.css" triggers this error because the validator encounters the colon in https: where it expects a valid MIME type token.
Another common cause is copying type values from other contexts (such as XML namespaces or schema references) that use URL-like strings, and mistakenly applying them to the type attribute.
Why this matters
- Standards compliance: The HTML specification requires the type attribute to contain a valid MIME type. Invalid values violate the spec and may cause browsers to misinterpret or ignore the linked resource.
- Browser behavior: Browsers use the type attribute as a hint for how to handle the resource. An invalid MIME type could lead the browser to skip loading the resource entirely, causing missing styles, icons, or other assets.
- Maintainability: Incorrect attribute values signal to other developers (and automated tools) that something is misconfigured, making the code harder to maintain.
How to fix it
- Check that type contains a valid MIME type, not a URL or other string. Common valid values include text/css, image/png, image/x-icon, image/svg+xml, and application/rss+xml.
- Ensure URLs are in the href attribute, not type.
- Consider removing type entirely. For stylesheets, modern browsers default to text/css, so type="text/css" is optional. For many use cases, the type attribute can be safely omitted.
Examples
❌ Incorrect: URL used as the type value
<link rel="stylesheet" type="https://example.com/style.css">
The validator sees the colon in https: and reports the error because this is a URL, not a MIME type.
❌ Incorrect: Attributes swapped
<link rel="icon" type="https://example.com/favicon.png" href="image/png">
Here the type and href values have been accidentally swapped.
✅ Correct: Valid MIME type with proper href
<link rel="icon" type="image/png" href="https://example.com/favicon.png">
✅ Correct: Stylesheet with valid type
<link rel="stylesheet" type="text/css" href="/css/style.css">
✅ Correct: Stylesheet without type (also valid)
<link rel="stylesheet" href="/css/style.css">
Since browsers default to text/css for stylesheets, omitting type is perfectly valid and keeps your markup cleaner.
✅ Correct: RSS feed link
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/feed.xml">
The <link> element defines a relationship between the current document and an external resource. It’s most often used in the <head> to load stylesheets, declare icons, specify canonical URLs, or provide metadata. The HTML specification requires that at least one of href, itemprop, property, rel, or resource be present on any <link> element. A bare <link> tag with none of these attributes has no defined purpose and is therefore invalid.
This validation error typically occurs in a few scenarios:
- A <link> element was added as a placeholder and never completed.
- Attributes were accidentally removed or misspelled during editing.
- A templating engine or build tool generated an incomplete <link> tag.
- The rel attribute was omitted when only href was technically sufficient, but the developer intended to specify a relationship.
While browsers may silently ignore an empty <link> element, leaving it in your markup creates clutter, signals incomplete code, and violates the HTML standard. Keeping your HTML valid ensures predictable behavior across browsers and assistive technologies.
How to fix it
Check every <link> element in your document and make sure it includes at least one of the required attributes. In practice, most <link> elements should have both rel and href:
- rel — specifies the relationship (e.g., stylesheet, icon, canonical, preconnect).
- href — provides the URL of the linked resource.
- itemprop — used for microdata markup.
- property — used for RDFa or Open Graph metadata.
- resource — used in RDFa to identify a resource by URI.
If a <link> element has no valid purpose, remove it entirely.
Examples
Invalid: <link> with no required attributes
<link type="text/css">
The type attribute alone does not satisfy the requirement. The validator will flag this element.
Invalid: <link> with only crossorigin
<link crossorigin="anonymous">
crossorigin is not one of the required attributes, so this is still invalid.
Valid: stylesheet with rel and href
<link rel="stylesheet" href="styles.css">
Valid: favicon with rel and href
<link rel="icon" href="/favicon.ico" type="image/x-icon">
Valid: preconnect hint
<link rel="preconnect" href="https://fonts.googleapis.com">
Valid: canonical URL
<link rel="canonical" href="https://example.com/page">
Valid: Open Graph metadata with property
<link property="og:image" href="https://example.com/image.png">
Valid: microdata with itemprop
<link itemprop="url" href="https://example.com">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="/favicon.ico">
<link rel="canonical" href="https://example.com/my-page">
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Each <link> element in this document has both a rel and an href attribute, making them valid and clearly communicating their purpose to browsers and validators.
The <link> element connects your HTML document to external resources like stylesheets, icons, fonts, and prefetched pages. According to the HTML specification, a <link> element must include at least one of rel, itemprop, or property so that its purpose is clearly defined. A bare <link> with only an href is meaningless—it points to a resource but doesn’t explain what that resource is for. The validator raises this error to enforce that every <link> carries semantic meaning.
This matters for several reasons. Browsers rely on these attributes to decide how to handle the linked resource. A <link> with rel="stylesheet" triggers CSS loading, while rel="icon" tells the browser to use the resource as a favicon. Without one of the required attributes, browsers may ignore the element entirely, leading to missing styles, icons, or other resources. It also affects accessibility tools and search engines that parse your markup for structured data.
Understanding the three attributes
-
rel — The most common attribute. It defines the relationship between your document and the linked resource. Examples include stylesheet, icon, preconnect, preload, canonical, and alternate. Most <link> elements in practice use rel.
-
itemprop — Used when the <link> element is part of an HTML Microdata structure. It specifies a property name within an itemscope, linking to a URL as the property’s value. This is commonly seen with Schema.org vocabularies.
-
property — Used with RDFa metadata (such as Open Graph tags). It defines a metadata property for the document, like og:image or schema:citation.
You only need one of these three attributes to satisfy the requirement, though you can combine them when appropriate.
Examples
Invalid: <link> with no relationship attribute
This triggers the validation error because the element has no rel, itemprop, or property attribute:
<head>
<title>My Page</title>
<link href="styles.css">
</head>
Fixed: adding rel for a stylesheet
<head>
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
</head>
Fixed: common uses of rel
<head>
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="canonical" href="https://example.com/page">
</head>
Fixed: using itemprop with Microdata
When a <link> appears inside an element with itemscope, use itemprop to define a property that takes a URL value:
<div itemscope itemtype="https://schema.org/Article">
<h2 itemprop="name">Understanding HTML Validation</h2>
<link itemprop="mainEntityOfPage" href="https://example.com/article">
</div>
Fixed: using property with RDFa / Open Graph
Open Graph meta tags for social sharing commonly use the property attribute. While <meta> is more typical for Open Graph, <link> with property is valid for URL-type values:
<head>
<title>My Page</title>
<link property="og:image" href="https://example.com/image.jpg">
<link property="schema:citation" href="https://example.com/source.html">
</head>
Invalid: typo or misplaced attribute
Sometimes this error appears because of a misspelled attribute name:
<head>
<title>My Page</title>
<link rел="stylesheet" href="styles.css">
</head>
Double-check that rel is spelled correctly and isn’t accidentally omitted when copying markup from templates or code snippets.
Quick fix checklist
- Linking to a stylesheet, icon, font, or other resource? Add the appropriate rel value.
- Defining Microdata properties? Use itemprop within an itemscope context.
- Adding RDFa or Open Graph metadata? Use property with the correct vocabulary prefix.
- Still seeing the error? Check for typos in the attribute name or ensure the attribute isn’t accidentally empty.
The <link> element is used to define relationships between the current document and external resources — most commonly stylesheets, favicons, and preloaded assets. According to the HTML specification, <link> elements belong in the <head> section of a document (with a narrow exception for body-ok link types like certain rel="stylesheet" uses, though validators still expect them in the <head> for clarity and correctness).
This error typically happens in a few common scenarios:
- A <link> tag is accidentally placed inside the <body> element.
- A <link> tag appears after the closing </head> tag but before the <body> tag.
- A <link> tag is placed after the closing </html> tag, often by a CMS, template engine, or plugin injecting code at the end of the document.
- The </head> tag is missing or misplaced, causing the browser to implicitly close the <head> before reaching the <link>.
When the validator encounters a <link> element outside its expected context, it reports it as a “stray” start tag — meaning the element has been found somewhere it doesn’t belong.
Why this matters
Standards compliance: The HTML specification defines <link> as metadata content that belongs in <head>. Placing it elsewhere produces invalid HTML.
Unpredictable behavior: Browsers will attempt to handle misplaced <link> elements, but the behavior can be inconsistent. A stylesheet <link> found in the <body> may trigger a flash of unstyled content (FOUC) as the browser re-renders the page after discovering the new stylesheet.
Performance: Stylesheets linked from the <head> are discovered early during parsing, allowing the browser to fetch and apply them before rendering begins. Misplaced <link> elements can delay resource loading and degrade the user experience.
Maintainability: Keeping all <link> elements in the <head> makes the document structure predictable and easier for other developers to maintain.
How to fix it
- Locate the stray <link> element — the validator will indicate the line number where the issue occurs.
- Move it into the <head> section — place it after the opening <head> tag and before the closing </head> tag.
- Check that your </head> tag exists and is in the right place — a missing or misplaced </head> can cause elements you thought were in the head to actually end up in the body.
- Review template or CMS output — if you use a static site generator, CMS, or framework, check that plugins or includes aren’t injecting <link> tags outside the <head>.
Examples
❌ <link> placed inside the <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<link rel="stylesheet" href="css/app.css">
<h1>Hello</h1>
</body>
</html>
❌ <link> placed after the closing </html> tag
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
<link rel="stylesheet" href="css/app.css">
❌ Missing </head> causes <link> to become stray
In this example, the </head> tag is missing. The browser encounters the <body> tag and implicitly closes the <head>, but the <link> after it becomes stray content between the implicit head closure and the body.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<body>
<link rel="stylesheet" href="css/app.css">
<h1>Hello</h1>
</body>
</html>
✅ Correct: all <link> elements inside <head>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<link rel="stylesheet" href="css/app.css">
<link rel="icon" href="favicon.ico">
</head>
<body>
<h1>Hello</h1>
</body>
</html>
All <link> elements are placed within the <head> section, the </head> closing tag is present, and the document structure is clean and valid.
The <link> element is used to define relationships between the current document and external resources—most commonly stylesheets, favicons, and preloaded assets. It is a void element (it has no closing tag) and it produces no rendered content on the page. Because <link> elements are inherently non-visible and already absent from the accessibility tree, the aria-hidden attribute serves no purpose on them.
The aria-hidden attribute is designed to control the visibility of rendered content for assistive technologies like screen readers. When set to "true" on a visible element, it tells assistive technologies to skip that element and its descendants. Applying it to a <link> element is contradictory—you’re trying to hide something from assistive technologies that was never exposed to them in the first place. The HTML specification explicitly disallows this combination, and the W3C validator will flag it as an error.
This issue sometimes arises when developers apply aria-hidden broadly through templating systems, JavaScript frameworks, or build tools that inject attributes across multiple element types without distinguishing between visible content elements and metadata elements. It can also happen when copying attribute patterns from <a> elements (which share the word “link” conceptually but are entirely different elements) onto <link> elements.
How to fix it
The fix is straightforward: remove the aria-hidden attribute from the <link> element. No replacement or alternative attribute is needed because <link> elements are already invisible to assistive technologies.
If your original intent was to hide a visible element from screen readers, make sure you’re applying aria-hidden to the correct element—a rendered content element such as <div>, <span>, <img>, or <a>, not a metadata element like <link>.
Examples
Incorrect: aria-hidden on a <link> element
<link rel="stylesheet" href="styles.css" aria-hidden="true">
<link rel="icon" href="favicon.ico" aria-hidden="true">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" aria-hidden="true">
Correct: <link> without aria-hidden
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="preload" href="font.woff2" as="font" type="font/woff2">
Correct: aria-hidden used on a visible element instead
If you need to hide a decorative element from screen readers, apply aria-hidden to the rendered element itself:
<link rel="stylesheet" href="styles.css">
<div aria-hidden="true">
<img src="decorative-swirl.png" alt="">
</div>
Incorrect vs. correct in a full document
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<!-- Incorrect: aria-hidden on link -->
<link rel="stylesheet" href="styles.css" aria-hidden="true">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<!-- Correct: no aria-hidden on link -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
In earlier versions of HTML, the charset attribute on <link> was used to indicate the character encoding of the linked resource (such as a stylesheet or other external file). The HTML Living Standard has since made this attribute obsolete. Modern browsers determine the character encoding of linked resources through the Content-Type HTTP response header — for example, Content-Type: text/css; charset=UTF-8 — or by using default encoding rules defined in the relevant specification (CSS files, for instance, default to UTF-8).
Using the obsolete charset attribute creates several issues. It gives a false sense of control over encoding, since browsers don’t actually use it. It also clutters your markup with unnecessary attributes, and it may signal to other developers that this is the mechanism being relied upon for encoding, when in reality it has no effect. For standards compliance and clean, future-proof markup, it should be removed.
The fix involves two steps: first, remove the charset attribute from your HTML; second, ensure your web server sends the correct Content-Type header for the linked resources.
Examples
Incorrect: using charset on a <link> element
<link rel="stylesheet" href="styles.css" charset="UTF-8">
<link rel="stylesheet" href="print.css" charset="iso-8859-1">
Both of these will trigger the validation warning because the charset attribute is obsolete on <link>.
Correct: charset attribute removed
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="print.css">
Simply removing the attribute resolves the validation error. The server should handle encoding declaration instead.
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example Page</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Configuring the server to declare character encoding
Once you’ve removed the charset attribute, make sure your web server sends the proper Content-Type header with the linked resources. Most modern servers already default to UTF-8 for CSS and JavaScript files, but you can configure this explicitly.
Apache
Add the following to your .htaccess file or server configuration:
<FilesMatch "\.css$">
AddCharset UTF-8 .css
</FilesMatch>
This causes Apache to serve CSS files with the header Content-Type: text/css; charset=UTF-8.
Nginx
Add a charset directive to the relevant location block in your server configuration:
location ~* \.css$ {
charset utf-8;
}
Node.js (Express)
Set the Content-Type header explicitly when serving the file:
app.get('/styles.css', function(req, res) {
res.setHeader('Content-Type', 'text/css; charset=UTF-8');
res.sendFile(__dirname + '/styles.css');
});
If you’re using Express’s built-in static file middleware (express.static), it typically sets correct content types automatically based on file extensions.
Verifying the fix
You can confirm that the server is sending the correct header by opening your browser’s developer tools, navigating to the Network tab, clicking on the linked resource, and inspecting the Content-Type response header. It should include charset=UTF-8 (or whichever encoding your file uses). Once the attribute is removed from your HTML and the server is configured correctly, the validation warning will be resolved.
The <a> element with an href attribute is one of HTML’s most fundamental interactive elements. Browsers and assistive technologies inherently recognize it as a link — it’s focusable via the Tab key, activatable with Enter, and announced as “link” by screen readers. This built-in behavior is part of the element’s implicit ARIA role, which is link.
When you explicitly add role="link" to an <a href="..."> element, you’re telling assistive technologies something they already know. The W3C validator flags this as unnecessary because it violates the principle of not redundantly setting ARIA roles that match an element’s native semantics. This principle is codified in the first rule of ARIA use: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
While a redundant role="link" won’t typically break anything, it creates noise in your markup. It can also signal to other developers that the role is necessary, leading to confusion or cargo-cult patterns. Clean, semantic HTML that relies on native roles is easier to maintain and less error-prone.
The role="link" attribute is legitimately useful when a non-interactive element like a <span> or <div> needs to behave as a link. In that case, you must also manually implement keyboard interaction (focus via tabindex, activation via Enter key handling) and provide an accessible name. But when you already have a proper <a> element with href, all of that comes for free — no ARIA needed.
Examples
❌ Incorrect: redundant role="link" on an anchor
<a href="/about" role="link">About Us</a>
The role="link" is redundant here because the <a> element with href already has an implicit role of link.
✅ Correct: anchor without redundant role
<a href="/about">About Us</a>
Simply remove the role="link" attribute. The browser and assistive technologies already treat this as a link.
✅ Correct: using role="link" on a non-semantic element (when necessary)
<span role="link" tabindex="0" onclick="location.href='/about'" onkeydown="if(event.key==='Enter') location.href='/about'">
About Us
</span>
This is the legitimate use case for role="link" — when you cannot use a native <a> element and need to make a non-interactive element behave like a link. Note the additional work required: tabindex="0" for keyboard focusability, a click handler, and a keydown handler for Enter key activation. Using a proper <a> element avoids all of this extra effort.
❌ Incorrect: multiple anchors with redundant roles
<nav>
<a href="/" role="link">Home</a>
<a href="/products" role="link">Products</a>
<a href="/contact" role="link">Contact</a>
</nav>
✅ Correct: clean navigation without redundant roles
<nav>
<a href="/">Home</a>
<a href="/products">Products</a>
<a href="/contact">Contact</a>
</nav>
Ready to validate your sites?
Start your free trial today.