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.
How the sizes Attribute Works
The sizes attribute works alongside srcset to enable responsive images. When you provide srcset with width descriptors (e.g., 400w, 800w), the browser needs to know how wide the image slot will actually be on screen so it can pick the best candidate. That's what sizes provides — a comma-separated list of size descriptors that tell the browser the rendered width of the image under various viewport conditions.
Each entry in the list follows this pattern:
- Optional media condition followed by a CSS length:
(max-width: 600px) 100vw - A final fallback length with no media condition:
33vw
The browser evaluates media conditions from left to right and uses the length from the first matching condition. If no condition matches, the fallback is used.
Why This Error Occurs
The validator checks that every length token in sizes uses one of the recognized CSS absolute or relative length units: em, ex, ch, rem, cap, ic, vw, vh, vmin, vmax, cm, mm, q, in, pc, pt, px, and their small/large/dynamic viewport variants (svw, lvw, dvw, svh, lvh, dvh, svi, lvi, dvi, svb, lvb, dvb, svmin, lvmin, dvmin, svmax, lvmax, dvmax).
Percentages (%) are not allowed. This is a common point of confusion — while % is valid in most CSS contexts, the sizes attribute explicitly forbids it because a percentage would be ambiguous (percentage of what?). The vw unit is typically the correct replacement when you want to express a fraction of the viewport width.
Here are the most common mistakes that trigger this error:
- Missing units:
sizes="100"— a bare number has no meaning without a unit. - Using percentages:
sizes="50%"— use50vwinstead. - Typos in unit names:
100pxx,100vws,50 vw(with a space between number and unit). - Multiple lengths in a single entry:
sizes="(min-width: 800px) 50vw 400px"— each entry must contain exactly one length. - Using
calc()incorrectly: Whilecalc()is valid insizes, the expressions inside it must also use valid units.
Why It Matters
When the sizes value is malformed, browsers fall back to a default of 100vw, which means every image is treated as if it spans the full viewport width. This defeats the purpose of responsive images — the browser may download unnecessarily large files on small screens, wasting bandwidth and slowing page loads. Valid sizes values are essential for proper image optimization.
Additionally, invalid HTML can cause unpredictable behavior across different browsers and versions. Standards-compliant markup ensures consistent rendering and forward compatibility.
How to Fix It
- Find the position indicated in the error message (the "at Z" part) — this tells you exactly where in the
sizesstring the invalid token was found. - Check for bare numbers and add the appropriate unit (
px,vw,em, etc.). - Replace
%withvwif you intended a percentage of the viewport width. - Fix any typos in unit names.
- Ensure each comma-separated entry has exactly one length, optionally preceded by a media condition in parentheses.
- Verify there's no space between the number and its unit —
100vwis correct,100 vwis not.
Examples
❌ Bare number without a unit
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100, 400"
alt="A landscape photo">
✅ Fixed: add vw and px units
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 400px"
alt="A landscape photo">
❌ Using invalid percentage
<img
src="banner-800.jpg"
srcset="banner-800.jpg 800w, banner-1600.jpg 1600w"
sizes="(max-width: 700px) 100%, 80%"
alt="Promotional banner">
✅ Fixed: replace % with vw
<img
src="banner-800.jpg"
srcset="banner-800.jpg 800w, banner-1600.jpg 1600w"
sizes="(max-width: 700px) 100vw, 80vw"
alt="Promotional banner">
❌ Multiple lengths in one entry
<img
src="hero-640.jpg"
srcset="hero-640.jpg 640w, hero-1280.jpg 1280w"
sizes="(min-width: 800px) 50vw 400px, 100vw"
alt="Hero image">
✅ Fixed: one length per entry, separated by commas
<img
src="hero-640.jpg"
srcset="hero-640.jpg 640w, hero-1280.jpg 1280w"
sizes="(min-width: 800px) 50vw, 100vw"
alt="Hero image">
❌ Typo in unit name
<img
src="thumb-320.jpg"
srcset="thumb-320.jpg 320w, thumb-640.jpg 640w"
sizes="320pxx"
alt="Thumbnail">
✅ Fixed: correct the unit
<img
src="thumb-320.jpg"
srcset="thumb-320.jpg 320w, thumb-640.jpg 640w"
sizes="320px"
alt="Thumbnail">
✅ Multiple media conditions with a fallback
<img
src="photo-640.jpg"
srcset="photo-640.jpg 640w, photo-960.jpg 960w, photo-1280.jpg 1280w"
sizes="(min-width: 1200px) 800px, (min-width: 800px) 60vw, 90vw"
alt="Landscape photo">
✅ Using calc() with valid units
<img
src="article-img-400.jpg"
srcset="article-img-400.jpg 400w, article-img-800.jpg 800w"
sizes="(min-width: 960px) calc(50vw - 2rem), 100vw"
alt="Article illustration">
The calc() function is valid inside sizes and is useful when the image width depends on a combination of viewport size and fixed spacing like padding or margins. Just make sure every value inside calc() also uses valid units.
The sizes attribute tells the browser how wide an image will be displayed at different viewport sizes, so it can choose the best candidate from srcset. It only applies when srcset uses width descriptors (e.g., 480w, 800w). The value is a comma-separated list where:
- Each entry (except the last) is a media condition in parentheses followed by a CSS length — for example,
(min-width: 800px) 50vw. - The final entry is a fallback length with no media condition — for example,
100vw.
The browser evaluates the media conditions from left to right and uses the length from the first one that matches. If none match, it uses the fallback.
The media condition syntax mirrors CSS media queries but without the @media keyword. You can use logical operators like and, or, and not, and features like min-width, max-width, orientation, etc.
Why This Error Occurs
The validator parses the sizes value according to the HTML living standard and CSS Media Queries specification. A parse error is triggered when the value doesn't conform to the expected grammar. Common causes include:
- Missing parentheses around the media condition:
min-width: 600px 50vwinstead of(min-width: 600px) 50vw. - Missing length after a media condition:
(min-width: 800px)with no length following it. - Invalid or missing units on the length:
50instead of50vw,50px, or50rem. - Trailing commas or extra separators:
(min-width: 600px) 50vw, , 100vw. - Invalid media feature syntax: typos like
(minwidth: 600px)or using unsupported tokens. - Using
sizeswith pixel-density descriptors: whensrcsetuses1x,2xinstead of width descriptors, thesizesattribute is meaningless and can confuse validators.
Why It Matters
- Browser image selection: Browsers rely on a correctly parsed
sizesvalue to pick the optimal image fromsrcset. A malformed value causes the browser to fall back to a default size (typically100vw), which can result in downloading unnecessarily large images on small screens or blurry images on large screens. - Standards compliance: Invalid
sizesvalues violate the HTML specification and may behave unpredictably across different browsers. - Performance: Correct
sizesvalues are essential for responsive image optimization. Without them, you lose the bandwidth savings thatsrcsetwith width descriptors is designed to provide.
How to Fix It
- Wrap every media condition in parentheses:
(min-width: 600px), notmin-width: 600px. - Always include a valid CSS length after each condition:
(min-width: 600px) 50vw, not just(min-width: 600px). - End with a fallback length that has no condition:
100vw. - Use valid CSS length units:
vw,px,em,rem, orcalc()expressions. - Remove trailing or duplicate commas.
- Omit
sizesentirely if yoursrcsetuses pixel-density descriptors (1x,2x).
Examples
Incorrect: missing parentheses around the media condition
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="min-width: 600px 50vw, 100vw"
alt="A landscape photo">
Correct: parentheses added
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A landscape photo">
Incorrect: missing length after the media condition
<img
src="banner-800.jpg"
srcset="banner-400.jpg 400w, banner-800.jpg 800w"
sizes="(min-width: 700px), 100vw"
alt="A promotional banner">
The first entry (min-width: 700px) has no length value — the comma makes it look like a separate entry, but it's incomplete.
Correct: length added after the condition
<img
src="banner-800.jpg"
srcset="banner-400.jpg 400w, banner-800.jpg 800w"
sizes="(min-width: 700px) 60vw, 100vw"
alt="A promotional banner">
Incorrect: using sizes with pixel-density descriptors
<img
src="avatar.jpg"
srcset="avatar@1x.jpg 1x, avatar@2x.jpg 2x"
sizes="(min-width: 600px) 80px, 40px"
alt="User avatar">
The sizes attribute is only meaningful with width descriptors (w). When srcset uses density descriptors (1x, 2x), omit sizes.
Correct: sizes removed for density descriptors
<img
src="avatar.jpg"
srcset="avatar@1x.jpg 1x, avatar@2x.jpg 2x"
alt="User avatar">
Incorrect: trailing comma
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(min-width: 800px) 800px, 100vw,"
alt="Hero image">
Correct: trailing comma removed
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(min-width: 800px) 800px, 100vw"
alt="Hero image">
Using sizes on a <source> inside <picture>
<picture>
<source
type="image/webp"
srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(min-width: 1200px) 1200px, (min-width: 800px) 800px, 100vw">
<img
src="hero-800.jpg"
srcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 1200px) 1200px, (min-width: 800px) 800px, 100vw"
alt="Hero image">
</picture>
Using compound media conditions
You can combine conditions with and:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(min-width: 900px) and (orientation: landscape) 50vw, 100vw"
alt="A sample photo">
Full valid document
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Responsive Image Example</title>
</head>
<body>
<img
src="pic-800.jpg"
srcset="pic-400.jpg 400w, pic-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A picture demonstrating valid sizes usage">
</body>
</html>
The sizes attribute on a <source> element contains a media condition or length value with malformed syntax, likely a missing or misspelled CSS function name where one is expected.
The sizes attribute tells the browser how wide an image will be displayed under various layout conditions. Its value is a comma-separated list of entries, each consisting of an optional media condition and a length. The length portion must be a valid CSS length, which can include plain values like 50vw or 100px, but also CSS math functions like calc(), min(), max(), and clamp().
This error fires when the validator's parser hits a spot in the sizes value where it expects a CSS function name but finds something else. Common causes:
- A missing function name, such as writing
(100vw - 2rem)instead ofcalc(100vw - 2rem). Parenthesized math expressions require an explicitcalc()wrapper. - A typo in a function name, like
clac(...)instead ofcalc(...). - Nesting errors or misplaced parentheses that confuse the parser about where a function should begin.
Invalid example
<picture>
<source
srcset="image-800.jpg 800w, image-1200.jpg 1200w"
sizes="(min-width: 768px) (100vw - 4rem), 100vw"
type="image/jpeg">
<img src="image-800.jpg" alt="A mountain landscape">
</picture>
The expression (100vw - 4rem) is not a valid CSS length on its own. The validator expects a function name before the opening parenthesis.
Fixed example
<picture>
<source
srcset="image-800.jpg 800w, image-1200.jpg 1200w"
sizes="(min-width: 768px) calc(100vw - 4rem), 100vw"
type="image/jpeg">
<img src="image-800.jpg" alt="A mountain landscape">
</picture>
Wrapping the arithmetic in calc() makes it a valid CSS length expression and resolves the validation error.
The sizes attribute tells the browser how wide an image will be displayed at various viewport sizes, so it can choose the best candidate from srcset before the CSS has loaded. Each entry in the sizes list is either a media condition paired with a CSS length, or a bare fallback length at the end. The HTML specification requires these lengths to be valid CSS <length> values, which means a number followed by a recognized unit. The full list of accepted units includes px, em, ex, ch, rem, vw, vh, vmin, vmax, cm, mm, in, pt, pc, and newer viewport units like svw, lvw, dvw, svh, lvh, dvh, and others.
The calc() function is also permitted, which lets you combine units — for example, calc(100vw - 2rem).
Why this matters
The sizes attribute is parsed before layout occurs, meaning the browser relies on it being syntactically correct to make smart download decisions. An invalid value forces the browser to fall back to a default size (typically 100vw), which can lead to downloading images that are far too large or too small. Beyond performance, an invalid sizes value violates the HTML specification, and some browsers may handle the malformed input inconsistently, leading to unpredictable behavior across devices.
Common mistakes
- Missing unit entirely: Writing
800instead of800px. Bare numbers without units are not valid CSS lengths (the only exception in CSS is0, but even0should have a unit insizesfor clarity). - Using percentages: Writing
50%instead of50vw. Percentages are not allowed insizesbecause the browser hasn't performed layout yet and doesn't know what the percentage would be relative to. Use viewport units likevwinstead. - Typos or extra characters: A stray character, misplaced comma, or missing whitespace can shift the parser's position and cause it to find unexpected tokens where it expects a unit.
- Using
autoon<source>: Theautokeyword forsizesis only valid on<img>elements (and requires theloading="lazy"attribute). Using it on<source>will trigger a validation error.
How to fix it
- Find the position indicated in the error message ("at Z") to locate the problematic value.
- If the value is a bare number, append the correct unit (e.g., change
600to600px). - If the value uses
%, replace it with a viewport-relative unit likevw. - Ensure commas separate each media-condition/length pair, and that the list ends with a fallback length.
Examples
Adding a missing unit
The value 800 in the sizes attribute is not a valid CSS length because it has no unit.
<!-- ❌ Invalid: "800" is missing a unit -->
<img
src="hero-800.jpg"
srcset="hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 900px) 800, 100vw"
alt="Hero image">
<!-- ✅ Valid: "800px" has a proper unit -->
<img
src="hero-800.jpg"
srcset="hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 900px) 800px, 100vw"
alt="Hero image">
Replacing a percentage with a viewport unit
Percentages are not permitted in sizes. Use vw to express a width relative to the viewport.
<!-- ❌ Invalid: "50%" is not an allowed unit in sizes -->
<img
src="card-600.jpg"
srcset="card-600.jpg 600w, card-900.jpg 900w"
sizes="50%"
alt="Product card">
<!-- ✅ Valid: "50vw" means 50% of the viewport width -->
<img
src="card-600.jpg"
srcset="card-600.jpg 600w, card-900.jpg 900w"
sizes="50vw"
alt="Product card">
Fixing the issue on a <source> element
The same rules apply to <source> elements inside <picture>. Every length value needs a valid unit.
<!-- ❌ Invalid: "600" on <source> is missing a unit -->
<picture>
<source
type="image/webp"
srcset="hero-600.webp 600w, hero-1200.webp 1200w"
sizes="(min-width: 768px) 600, 100vw">
<img
src="hero-600.jpg"
srcset="hero-600.jpg 600w, hero-1200.jpg 1200w"
sizes="(min-width: 768px) 600px, 100vw"
alt="Hero image">
</picture>
<!-- ✅ Valid: both <source> and <img> use proper units -->
<picture>
<source
type="image/webp"
srcset="hero-600.webp 600w, hero-1200.webp 1200w"
sizes="(min-width: 768px) 600px, 100vw">
<img
src="hero-600.jpg"
srcset="hero-600.jpg 600w, hero-1200.jpg 1200w"
sizes="(min-width: 768px) 600px, 100vw"
alt="Hero image">
</picture>
Using calc() for mixed-unit sizes
You can use calc() to combine different units, which is especially useful when accounting for padding or margins.
<img
src="article-800.jpg"
srcset="article-800.jpg 800w, article-1200.jpg 1200w"
sizes="(min-width: 1024px) calc(100vw - 4rem), 100vw"
alt="Article image">
Multiple slots with different conditions
A well-formed sizes attribute with several media conditions, ending with a unitless-condition fallback length:
<picture>
<source
type="image/avif"
srcset="banner-480.avif 480w, banner-960.avif 960w, banner-1440.avif 1440w"
sizes="(min-width: 1200px) 960px, (min-width: 600px) 80vw, 100vw">
<img
src="banner-480.jpg"
srcset="banner-480.jpg 480w, banner-960.jpg 960w, banner-1440.jpg 1440w"
sizes="(min-width: 1200px) 960px, (min-width: 600px) 80vw, 100vw"
alt="Responsive banner">
</picture>
The general pattern is: sizes="(condition) length, (condition) length, fallback-length". Every length in that list — including the fallback — must be a valid CSS length with a unit or a calc() expression. If the validator error points to a specific character position, inspect that exact spot for a missing unit, a stray %, or malformed whitespace and replace it with one of the recognized units.
A sizes attribute on a <source> element contains a length value that is missing valid CSS units or uses an unrecognized unit.
The sizes attribute tells the browser how wide an image will be displayed at a given viewport condition. Each entry in the sizes list is either a media condition paired with a source size value, or a default source size value. The source size value must be a valid CSS length, which means it requires a recognized unit such as px, vw, em, or rem. Bare numbers without units, percentage values (%), or made-up units are not allowed.
A common mistake is writing a bare number like 300 instead of 300px, or using % instead of vw. The % unit is explicitly disallowed in sizes because the browser needs to resolve image widths before layout is complete, and percentage lengths depend on a containing block that may not be known yet. Use vw (viewport width) when you want a percentage-like relative size.
The sizes attribute applies to <source> elements inside a <picture>, and also to <img> elements directly.
Invalid example
<picture>
<source
srcset="image-480.jpg 480w, image-800.jpg 800w"
sizes="(max-width: 600px) 100%, 50%"
type="image/jpeg">
<img src="image-800.jpg" alt="A sample photo">
</picture>
The validator rejects 100% and 50% because % is not a permitted unit in sizes.
Fixed example
<picture>
<source
srcset="image-480.jpg 480w, image-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
type="image/jpeg">
<img src="image-800.jpg" alt="A sample photo">
</picture>
Replace % with vw, or use any other valid CSS absolute or relative length unit (px, em, rem, etc.). A calc() expression with valid units also works, for example calc(100vw - 2rem).
A URL follows a specific structure defined by RFC 3986. The general format is:
scheme://host/path?query#fragment
The # character serves as the delimiter that introduces the fragment portion of the URL. It may only appear once in this role. Once the parser encounters the first #, everything after it is treated as the fragment identifier. A second # within that fragment is an illegal character because the fragment production in the URL specification does not permit unescaped # characters.
This validation error commonly arises from:
- Duplicate
#characters — e.g., accidentally including two hash marks like/#?param=value#section. - Misplaced query strings — putting
?key=valueafter the#instead of before it. While this may work in some single-page application routers, it results in the query being part of the fragment, and adding another#after that creates an invalid URL. - Copy-paste errors — assembling URLs from multiple parts and inadvertently introducing an extra
#.
This matters for several reasons. Browsers may handle malformed URLs inconsistently — some may silently truncate or ignore part of the URL, while others may fail to load the resource entirely. The W3C validator flags this because it violates the HTML specification's requirement that the src attribute contain a valid URL. Invalid URLs can also cause issues with assistive technologies, link sharing, and automated tools that parse your HTML.
How to fix it
- Locate all
#characters in the URL and determine which one is the intended fragment delimiter. - Remove any duplicate
#characters that were added by mistake. - Move query parameters before the fragment — the
?queryportion must come before the#fragmentin a well-formed URL. - Percent-encode if needed — if a literal
#must appear as data within the fragment or query value (not as a delimiter), encode it as%23.
Examples
Incorrect: multiple # characters
The second # inside the fragment is illegal:
<iframe src="https://example.com/#?secret=123#abc"></iframe>
Correct: query before fragment
Move the query string before the # so the URL has a proper structure:
<iframe src="https://example.com/?secret=123#abc"></iframe>
Correct: single fragment, no query
If you only need a fragment identifier, use a single #:
<iframe src="https://example.com/#abc"></iframe>
Correct: percent-encoding a literal # in a value
If the fragment itself must contain a # as data (not as a delimiter), percent-encode it:
<iframe src="https://example.com/?secret=123#color=%23ff0000"></iframe>
Here, %23ff0000 represents the literal string #ff0000 within the fragment value, which is valid because the # is encoded.
Incorrect: hash-based routing with duplicate #
Some single-page app embed URLs use hash-based routing, which can lead to accidental double hashes:
<iframe src="https://app.example.com/#/dashboard#settings"></iframe>
Correct: use a single fragment for the route
Restructure the URL to use a single # with a combined path:
<iframe src="https://app.example.com/#/dashboard/settings"></iframe>
Or, if the application supports it, use standard path-based routing instead:
<iframe src="https://app.example.com/dashboard/settings"></iframe>
A space character in the src attribute of an iframe is not valid in a URL. Spaces must be percent-encoded as %20 to produce a legal URL.
URLs follow the syntax rules defined in RFC 3986, which does not allow literal space characters anywhere in a URI, including the query string portion (the part after ?). When the HTML validator encounters a raw space in a URL attribute like src, it flags the value as malformed.
This commonly happens when query parameter values contain readable text, or when a URL is copied from a browser's address bar where the browser displays decoded spaces for readability. The fix is to replace each space with %20.
Some developers use + instead of %20 for spaces in query strings. The + convention originates from the application/x-www-form-urlencoded format used in HTML form submissions, and many servers accept it. However, %20 is the correct encoding according to RFC 3986 and will pass W3C validation without issues.
HTML examples
Invalid: space in the query string
<iframe
src="https://example.com/embed?title=my page&lang=en"
width="600"
height="400">
</iframe>
Valid: space encoded as %20
<iframe
src="https://example.com/embed?title=my%20page&lang=en"
width="600"
height="400">
</iframe>
When the W3C HTML Validator reports "Bad value X for attribute src on element iframe: Illegal character in query: [ is not allowed", it means your URL contains unencoded square brackets in the query portion (everything after the ?). While most modern browsers will silently handle these characters and load the resource anyway, the URL does not conform to the URI syntax defined in RFC 3986, which the HTML specification requires.
According to RFC 3986, square brackets are reserved characters that have a specific purpose: they are only permitted in the host component of a URI to denote IPv6 addresses (e.g., http://[::1]/). Anywhere else in the URI — including the query string — they must be percent-encoded. The HTML living standard (WHATWG) requires that URLs in attributes like src, href, and action be valid URLs, which means they must follow these encoding rules.
Why this matters
- Standards compliance: Invalid URLs cause W3C validation failures, which can indicate deeper issues in your markup.
- Interoperability: While mainstream browsers are forgiving, some HTTP clients, proxies, CDNs, or web application firewalls may reject or mangle URLs with unencoded square brackets.
- Consistent behavior: Percent-encoding reserved characters guarantees that the URL is interpreted the same way everywhere — in browsers, server logs, link checkers, and automated tools.
- Copy-paste reliability: When users or tools copy a URL from your HTML source, an already-encoded URL is less likely to break during transmission through email clients, messaging apps, or other systems.
How to fix it
Replace every occurrence of [ with %5B and ] with %5D within the query string of the URL. If you're generating URLs server-side or in JavaScript, use the language's built-in URL encoding functions rather than doing manual find-and-replace:
- JavaScript:
encodeURIComponent()or theURL/URLSearchParamsAPIs - PHP:
urlencode()orhttp_build_query() - Python:
urllib.parse.urlencode()orurllib.parse.quote()
These functions will automatically encode square brackets and any other reserved characters.
Examples
❌ Invalid — unencoded square brackets in query string
<iframe src="https://example.com/embed?filter[status]=active&filter[type]=video"></iframe>
The validator flags [ and ] as illegal characters in the query component.
✅ Valid — square brackets percent-encoded
<iframe src="https://example.com/embed?filter%5Bstatus%5D=active&filter%5Btype%5D=video"></iframe>
Replacing [ with %5B and ] with %5D resolves the error. The server receives the exact same parameter values — most server-side frameworks (PHP, Rails, etc.) automatically decode percent-encoded characters before processing them.
❌ Invalid — square brackets in a timestamp parameter
<iframe src="https://example.com/report?time=[2024-01-01]"></iframe>
✅ Valid — timestamp parameter properly encoded
<iframe src="https://example.com/report?time=%5B2024-01-01%5D"></iframe>
Generating encoded URLs in JavaScript
If you're setting the src dynamically, let the browser handle encoding for you:
<iframe id="report-frame"></iframe>
<script>
const url = new URL("https://example.com/embed");
url.searchParams.set("filter[status]", "active");
url.searchParams.set("filter[type]", "video");
document.getElementById("report-frame").src = url.toString();
</script>
The URLSearchParams API automatically percent-encodes the brackets, producing a valid URL in the src attribute.
A note on other elements
This same rule applies to any HTML attribute that accepts a URL — including href on <a> elements, action on <form> elements, and src on <script> or <img> elements. Whenever you place a URL in HTML, ensure all reserved characters in the query string are properly percent-encoded.
Backslashes are not valid delimiters in URLs according to the URL Living Standard. While some browsers may silently normalize backslashes to forward slashes, this behavior is non-standard and should not be relied upon. Using backslashes in URLs can lead to broken images, unexpected behavior across different browsers, and failures in environments that strictly follow URL specifications (such as HTTP servers, CDNs, or validation tools).
This issue commonly arises when developers copy file paths directly from a Windows file system — where \ is the directory separator — and paste them into HTML src attributes. It can also happen when server-side code generates URLs using OS-level path functions instead of URL-building utilities.
Beyond standards compliance, this matters for several practical reasons:
- Cross-browser reliability: Not all browsers or HTTP clients normalize backslashes the same way.
- Server compatibility: Many web servers interpret backslashes literally, resulting in 404 errors.
- Portability: Code with backslash paths may work in local development on Windows but break when deployed to a Linux-based server.
To fix the issue, locate every backslash in the src attribute value and replace it with a forward slash. This applies to all URL contexts, not just img elements — though the validator specifically flags it here.
Examples
❌ Incorrect: backslashes in the src path
<img src="images\photos\landscape.jpg" alt="Mountain landscape">
<img src="https://example.com\img\small\photo.png" alt="Example image">
Both of these use backslashes as path delimiters, which triggers the validation error.
✅ Correct: forward slashes in the src path
<img src="images/photos/landscape.jpg" alt="Mountain landscape">
<img src="https://example.com/img/small/photo.png" alt="Example image">
Simply replacing \ with / resolves the issue and produces a valid, portable URL.
❌ Incorrect: mixed delimiters
<img src="assets/images\banner\hero.webp" alt="Hero banner">
Even a single backslash in an otherwise valid path will trigger this error.
✅ Correct: consistent forward slashes
<img src="assets/images/banner/hero.webp" alt="Hero banner">
Tips to avoid this issue
- Don't copy-paste Windows file paths directly into HTML. Always convert backslashes to forward slashes.
- Use your editor's find-and-replace to search for
\withinsrcattributes across your project. - If generating URLs in server-side code, use URL-building functions rather than file-system path functions. For example, in Node.js, use the
urlmodule or template literals with/instead ofpath.join(), which uses\on Windows. - Run the W3C validator regularly during development to catch issues like this before deployment.
URLs follow strict syntax rules defined by RFC 3986. Within a URL's path segment, only a specific set of characters is allowed to appear literally. When a character falls outside this allowed set, it must be percent-encoded — represented as a % sign followed by two hexadecimal digits corresponding to the character's ASCII code. The W3C validator checks that every URL in your HTML conforms to these rules, and it flags any src value that contains raw illegal characters.
Characters that commonly trigger this error include:
| Character | Percent-encoded |
|---|---|
| (space) | %20 |
[ | %5B |
] | %5D |
{ | %7B |
} | %7D |
| | %7C |
^ | %5E |
` | %60 |
Other reserved characters like ?, #, @, !, $, &, ', (, ), *, +, ,, ;, and = also need encoding when used as literal data in a path segment rather than as URL delimiters. The % character itself must be encoded as %25 if it appears literally.
Why This Is a Problem
- Browser inconsistency: While many modern browsers silently fix malformed URLs, not all do. Some browsers, older user agents, or HTTP clients may fail to load the resource or interpret the URL differently, leading to broken images.
- Standards compliance: Invalid URLs violate the HTML specification, which requires that attribute values containing URLs conform to valid URL syntax.
- Interoperability: Servers, CDNs, proxies, and caching layers may handle illegal characters unpredictably, causing intermittent failures that are difficult to debug.
- Accessibility: If a URL is malformed and the image fails to load, users relying on assistive technologies may not receive the intended content, even when appropriate
alttext is provided.
How to Fix It
You have two main approaches:
- Percent-encode the illegal characters in the
srcvalue. Replace each offending character with its%XXequivalent. - Rename the file to use only URL-safe characters. Stick to lowercase letters, digits, hyphens (
-), underscores (_), and dots (.). This is the cleanest long-term solution.
If you're generating URLs programmatically, use your language's built-in URL encoding functions (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python).
Examples
Illegal characters in the filename
The square brackets in the src value are not allowed in a URL path segment:
<!-- ❌ Invalid: raw [ and ] in URL path -->
<img src="image[00].svg" alt="Company logo">
Fix by percent-encoding:
<!-- ✅ Valid: [ and ] are percent-encoded -->
<img src="image%5B00%5D.svg" alt="Company logo">
Fix by renaming the file:
<!-- ✅ Valid: filename uses only safe characters -->
<img src="image-00.svg" alt="Company logo">
Spaces in the filename
Spaces are one of the most common causes of this error:
<!-- ❌ Invalid: space in URL path -->
<img src="my photo.jpg" alt="Vacation photo">
<!-- ✅ Valid: space encoded as %20 -->
<img src="my%20photo.jpg" alt="Vacation photo">
<!-- ✅ Valid: filename uses a hyphen instead of a space -->
<img src="my-photo.jpg" alt="Vacation photo">
Curly braces in a template-like path
Sometimes filenames or paths contain curly braces from templating artifacts or naming conventions:
<!-- ❌ Invalid: raw { and } in URL path -->
<img src="icons/{home}.png" alt="Home icon">
<!-- ✅ Valid: curly braces percent-encoded -->
<img src="icons/%7Bhome%7D.png" alt="Home icon">
Best practice for file naming
The simplest way to avoid this error entirely is to adopt a consistent file naming convention that only uses URL-safe characters:
<!-- ✅ Valid: clean, URL-safe filenames -->
<img src="images/hero-banner-2024.webp" alt="Welcome banner">
<img src="photos/team_photo_01.jpg" alt="Our team">
Spaces in the src attribute of an img element are not valid URL characters and must be encoded as %20 or replaced with hyphens/underscores.
URLs follow the rules defined in RFC 3986, which does not allow literal space characters in any part of a URL. When a browser encounters a space in a src value, it may try to percent-encode it automatically, but this behavior is not guaranteed across all contexts. The W3C validator flags this because the HTML specification requires attribute values containing URLs to hold valid URL strings.
There are two ways to fix this: rename the file to remove spaces, or percent-encode the spaces as %20. Renaming is the better long term approach because it avoids encoding issues entirely and makes URLs easier to read and share.
Examples
Invalid: space in the file name
<img src="my photo.jpg" alt="A photo">
Valid: percent-encoded space
<img src="my%20photo.jpg" alt="A photo">
Valid: renamed file with no spaces
<img src="my-photo.jpg" alt="A photo">
URLs follow a strict syntax defined by the URL Living Standard and RFC 3986. Only a specific set of characters are allowed to appear literally in a URL's query string. Characters outside this set — such as pipes, square brackets, curly braces, and certain other symbols — must be percent-encoded. Percent-encoding replaces the character with a % sign followed by its two-digit hexadecimal ASCII code.
When the W3C HTML Validator encounters an <img> tag whose src attribute contains an illegal character in the query portion of the URL, it raises this error. The query string is the part of the URL that comes after the ? character.
Why this matters
- Browser inconsistency: While many modern browsers will silently fix malformed URLs, not all browsers or HTTP clients handle illegal characters the same way. Some may misinterpret the URL or fail to load the resource entirely.
- Standards compliance: Valid URLs are a foundational requirement for interoperable web content. Using illegal characters violates both the HTML and URL specifications.
- Interoperability: Automated tools, web crawlers, proxies, and content delivery networks may reject or mangle URLs containing unencoded special characters, leading to broken images.
- Accessibility: Screen readers and assistive technologies rely on valid markup. Malformed URLs can cause unexpected behavior in these tools.
Common illegal characters and their encodings
Here are characters frequently flagged by the validator:
| Character | Percent-encoded |
|---|---|
| (pipe) | %7C |
[ | %5B |
] | %5D |
{ | %7B |
} | %7D |
^ | %5E |
` (backtick) | %60 |
| (space) | %20 |
How to fix it
Identify the illegal characters in the src URL and replace each one with its corresponding percent-encoded value. If you're generating URLs dynamically with a programming language, use the language's built-in URL-encoding function (e.g., encodeURI() or encodeURIComponent() in JavaScript, urlencode() in PHP, urllib.parse.quote() in Python).
Examples
❌ Incorrect: unencoded pipe character in query string
<img src="https://example.com/image?filter=red|blue" alt="Filtered image">
The | character is not allowed literally in the query string.
✅ Correct: pipe character percent-encoded
<img src="https://example.com/image?filter=red%7Cblue" alt="Filtered image">
❌ Incorrect: unencoded square brackets in query string
<img src="https://example.com/image?size[width]=300&size[height]=200" alt="Resized image">
The [ and ] characters must be encoded.
✅ Correct: square brackets percent-encoded
<img src="https://example.com/image?size%5Bwidth%5D=300&size%5Bheight%5D=200" alt="Resized image">
❌ Incorrect: space in query string
<img src="https://example.com/image?caption=hello world" alt="Captioned image">
✅ Correct: space percent-encoded
<img src="https://example.com/image?caption=hello%20world" alt="Captioned image">
Encoding URLs dynamically
If your URLs are built in JavaScript, use encodeURIComponent() for individual query parameter values:
const filter = "red|blue";
const url = `https://example.com/image?filter=${encodeURIComponent(filter)}`;
// Result: "https://example.com/image?filter=red%7Cblue"
This ensures that any special characters in user-provided or dynamic values are properly encoded before being placed into the HTML.
URLs follow strict syntax rules defined by RFC 3986, which does not allow literal space characters. When the W3C validator encounters a space in the src attribute of an <img> element, it reports it as an illegal character in the scheme data. While most modern browsers will attempt to handle spaces in URLs by automatically encoding them, relying on this behavior is not standards-compliant and can lead to broken images in certain contexts, such as when URLs are processed by other tools, APIs, or older browsers.
This issue commonly arises when file names contain spaces (e.g., my photo.jpg) and the path is copied directly into the HTML. It can also happen when a URL is incorrectly constructed by concatenating strings with unencoded values.
Beyond validation, unencoded spaces can cause real problems. A URL with a space may break when shared, copied, or passed through redirects. Email clients and messaging apps may truncate the URL at the space. Search engine crawlers might fail to index the resource correctly.
How to fix it
There are several ways to resolve this issue:
- Percent-encode spaces as
%20— Replace every literal space in the URL with%20. - Rename the file — Remove spaces from file names entirely, using hyphens (
-), underscores (_), or camelCase instead. - Use proper URL encoding functions — If generating URLs dynamically (e.g., in JavaScript or a server-side language), use built-in encoding functions like
encodeURI()orencodeURIComponent().
Renaming files to avoid spaces is generally the best long-term practice, as it prevents encoding issues across your entire workflow.
Examples
❌ Invalid: spaces in the src attribute
<img src="images/my photo.jpg" alt="A vacation photo">
<img src="/assets/blog posts/header image.png" alt="Blog header">
Both of these will trigger the validation error because the src value contains literal space characters.
✅ Fixed: spaces encoded as %20
<img src="images/my%20photo.jpg" alt="A vacation photo">
<img src="/assets/blog%20posts/header%20image.png" alt="Blog header">
✅ Fixed: file renamed to remove spaces
<img src="images/my-photo.jpg" alt="A vacation photo">
<img src="/assets/blog-posts/header-image.png" alt="Blog header">
A note on + vs %20
You may see spaces encoded as + in some contexts (e.g., query strings in form submissions using application/x-www-form-urlencoded). However, + is not a valid substitute for a space in a URL path. Always use %20 for spaces in the src attribute.
<!-- ❌ Incorrect: + does not represent a space in a URL path -->
<img src="images/my+photo.jpg" alt="A vacation photo">
<!-- ✅ Correct -->
<img src="images/my%20photo.jpg" alt="A vacation photo">
In URLs, special characters that aren't part of the standard allowed set must be represented using percent-encoding (also called URL encoding). This works by replacing the character with a % followed by two hexadecimal digits representing its byte value — for example, a space becomes %20, and an ampersand becomes %26. Because % itself serves as the escape character in this scheme, any bare % that isn't followed by two hexadecimal digits creates an ambiguous, invalid URL.
When the W3C validator encounters a src attribute containing a % not followed by two valid hex digits, it cannot determine the intended character and reports the error. This issue typically arises in two scenarios:
- A literal percent sign in the URL — for instance, a query parameter like
?width=48%where the%is meant as an actual percent symbol. The%must be encoded as%25. - Incomplete or corrupted percent-encoding — such as
%2instead of%20, or%GZwhere the characters after%aren't valid hexadecimal digits (only0–9andA–F/a–fare valid).
Why this matters
- Browser inconsistency: While many browsers try to handle malformed URLs gracefully, behavior varies. Some browsers may misinterpret the intended resource path, leading to broken images or unexpected requests.
- Standards compliance: The URL Living Standard and RFC 3986 define strict rules for percent-encoding. Invalid URLs violate these standards.
- Reliability: Proxies, CDNs, and server-side software may reject or misroute requests with malformed URLs, causing images to fail to load in certain environments even if they work in your local browser.
How to fix it
- Find every bare
%in the URL that isn't followed by two hexadecimal digits. - If the
%is meant as a literal percent sign, replace it with%25. - If the
%is part of a broken encoding sequence (e.g.,%2or%GH), correct it to the intended two-digit hex code (e.g.,%20for a space). - Use proper URL encoding functions in your language or framework (e.g.,
encodeURIComponent()in JavaScript,urlencode()in PHP) when building URLs dynamically, rather than constructing them by hand.
Examples
Literal percent sign not encoded
<!-- ❌ Bad: bare "%" is not followed by two hex digits -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%">
<!-- ✅ Fixed: "%" encoded as "%25" -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%25">
Incomplete percent-encoding sequence
<!-- ❌ Bad: "%2" is incomplete — missing the second hex digit -->
<img alt="Photo" src="https://example.com/my%2photo.jpg">
<!-- ✅ Fixed: use "%20" for a space character -->
<img alt="Photo" src="https://example.com/my%20photo.jpg">
Non-hexadecimal characters after percent sign
<!-- ❌ Bad: "%zz" uses non-hex characters -->
<img alt="Logo" src="https://example.com/logo%zz.png">
<!-- ✅ Fixed: remove the erroneous sequence if it was unintentional -->
<img alt="Logo" src="https://example.com/logo.png">
Multiple encoding issues in one URL
<!-- ❌ Bad: bare "%" in query value and unencoded space -->
<img alt="Report" src="https://example.com/img?label=100%&name=my file.png">
<!-- ✅ Fixed: "%" → "%25", space → "%20" -->
<img alt="Report" src="https://example.com/img?label=100%25&name=my%20file.png">
Building URLs safely with JavaScript
When generating src values in code, use encodeURIComponent() to handle special characters automatically:
const label = "100%";
const name = "my file.png";
const src = `https://example.com/img?label=${encodeURIComponent(label)}&name=${encodeURIComponent(name)}`;
// Result: "https://example.com/img?label=100%25&name=my%20file.png"
This ensures every special character — including % — is correctly percent-encoded without manual effort.
The HTML specification requires that URL attributes like src contain valid URLs. While browsers are generally forgiving and may strip or ignore whitespace characters in URLs, including tabs (\t), newlines (\n), or carriage returns (\r) inside a src value is technically invalid. These characters are not legal within a URL according to both the HTML and URL standards.
Beyond standards compliance, there are practical reasons to fix this:
- Unpredictable behavior: Different browsers may handle embedded whitespace differently. Some might silently strip it, while others could encode it as
%0Aor%0D, leading to broken image requests. - Debugging difficulty: Invisible characters make URLs fail silently. The path looks correct in your editor, but the actual HTTP request goes to a different (malformed) URL.
- Accessibility and tooling: Screen readers, crawlers, and other tools that parse your HTML may not handle malformed URLs gracefully, potentially causing images to be skipped or reported as broken.
Common Causes
This issue typically appears in a few scenarios:
- Multi-line attributes in templates: When a URL is built dynamically in a template engine (e.g., PHP, Jinja, Handlebars), line breaks in the template code can leak into the rendered attribute value.
- Copy-paste errors: Copying a URL from a document, email, or another source may include hidden line breaks or tab characters.
- Code formatting: Some developers or auto-formatters may break long attribute values across multiple lines, inadvertently injecting newlines into the value.
- String concatenation: Building URLs through string concatenation in server-side code can introduce whitespace if variables contain trailing newlines.
Examples
Incorrect: Newline inside src value
The newline before the closing quote makes this an invalid URL:
<img src="images/photo.jpg
" alt="A scenic photo">
Incorrect: Tab character in src value
A tab character embedded in the middle of the URL path:
<img src="images/ photo.jpg" alt="A scenic photo">
Incorrect: Multi-line URL from template output
This can happen when a template engine outputs a URL with leading or trailing whitespace:
<img src="
https://example.com/images/photo.jpg
" alt="A scenic photo">
Correct: Clean, single-line src value
The URL is a single continuous string with no tabs, newlines, or carriage returns:
<img src="images/photo.jpg" alt="A scenic photo">
Correct: Long URL kept on one line
Even if the URL is long, it must remain on a single line without breaks:
<img src="https://cdn.example.com/assets/images/2024/photos/scenic-landscape-full-resolution.jpg" alt="A scenic photo">
How to Fix It
- Inspect the attribute value: Open the raw HTML source (not the rendered page) and look at the
srcvalue character by character. Use your editor's "show whitespace" or "show invisible characters" feature to reveal hidden tabs and line breaks. - Remove all whitespace inside the URL: Ensure the entire URL is on a single line with no tabs, newlines, or carriage returns anywhere between the opening and closing quotes.
- Check your templates: If the URL is generated dynamically, trim the output. Most languages offer a
trim()orstrip()function that removes leading and trailing whitespace including newlines. - Lint your HTML: Use the W3C validator or an HTML linter in your build pipeline to catch these issues automatically before deployment.
The src attribute on a <script> element points to a URL whose path contains a character that is not allowed there unless it is percent-encoded.
A URL path may only use a restricted set of characters. Symbols such as ^, spaces, square brackets, and backticks have to be written in their percent-encoded form, so a caret becomes %5E. The validator names the exact character it rejected, so look at the path segment it points to and either encode that character or change the URL so it no longer appears.
A common source of the ^ character is a version range copied from a package manager. A specifier like @^2 belongs in a package.json file, not in a CDN URL. When you load a script straight from a CDN, pin an explicit version instead.
Invalid example
<script src="https://cdn.example.com/pkg@^2/dist/app.js"></script>
Valid example
<script src="https://cdn.example.com/pkg@2.1.0/dist/app.js"></script>
URLs follow strict syntax rules defined by RFC 3986, which does not permit literal space characters anywhere in the URI — including path segments, query strings, and fragment identifiers. When the W3C HTML Validator encounters a space in the src attribute of a <script> element, it flags it as an illegal character because the attribute value is not a valid URL.
While most modern browsers will silently fix this by encoding the space before making the request, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that the
srcattribute contain a valid URL. A URL with a literal space is technically malformed. - Cross-browser reliability: Not all user agents, proxies, or CDNs handle malformed URLs the same way. What works in one browser may fail in another context.
- Interoperability: Other tools that consume your HTML — such as linters, crawlers, screen readers, and build pipelines — may not be as forgiving as browsers.
- Copy-paste and linking issues: Literal spaces in URLs cause problems when users copy links or when URLs appear in plain-text contexts like emails, where the space may break the URL in two.
How to fix it
You have three options, listed from most recommended to least:
- Rename the file or directory to eliminate spaces entirely (e.g., use hyphens or underscores). This is the cleanest solution.
- Percent-encode the space as
%20in thesrcattribute value. - Use a build tool or bundler that generates references with properly encoded or space-free paths automatically.
Avoid using + as a space replacement in path segments. The + character represents a space only in application/x-www-form-urlencoded query strings, not in URL path segments.
Examples
❌ Invalid: space in the path segment
<script src="https://example.com/media assets/app.js"></script>
The space between media and assets makes this an invalid URL.
✅ Fixed: percent-encode the space
<script src="https://example.com/media%20assets/app.js"></script>
Replacing the space with %20 produces a valid, standards-compliant URL.
✅ Better: rename to avoid spaces entirely
<script src="https://example.com/media-assets/app.js"></script>
Using a hyphen (or underscore) instead of a space is the preferred approach. It keeps URLs clean, readable, and free of encoding issues.
❌ Invalid: space in a local relative path
This issue isn't limited to absolute URLs. Relative paths trigger the same error:
<script src="js/my script.js"></script>
✅ Fixed: encode or rename the local file
<script src="js/my%20script.js"></script>
Or, better yet:
<script src="js/my-script.js"></script>
Multiple spaces and other special characters
If a URL contains multiple spaces or other special characters, each one must be individually encoded. For example, { becomes %7B and } becomes %7D. A quick reference for common characters:
| Character | Encoded form |
|---|---|
| Space | %20 |
[ | %5B |
] | %5D |
{ | %7B |
} | %7D |
<!-- Invalid -->
<script src="libs/my library [v2].js"></script>
<!-- Valid -->
<script src="libs/my%20library%20%5Bv2%5D.js"></script>
<!-- Best: rename the file -->
<script src="libs/my-library-v2.js"></script>
Note that this same rule applies to the src attribute on other elements like <img>, <iframe>, <audio>, and <video>, as well as the href attribute on <a> and <link>. Whenever you reference a URL in HTML, make sure it contains no literal spaces.
When the browser's HTML parser encounters a src attribute, it expects the value between the opening and closing quotes to be a valid URL. URLs have strict rules about which characters are permitted — a literal double quote (") is not one of them. If a " character appears inside the URL, the validator flags it as an illegal character in the query string (or other parts of the URL).
This error often doesn't stem from an intentionally embedded quote in the URL itself. Instead, it's usually a symptom of malformed HTML around the attribute. Common causes include:
- Stray quotes after the attribute value, such as writing
src="https://example.com/script.js"async""instead of properly separating attributes. - Accidentally doubled closing quotes, like
src="https://example.com/script.js"". - Boolean attributes given empty-string values incorrectly, such as
async""instead of justasync, which causes the parser to interpret the extra quotes as part of the precedingsrcvalue. - Copy-paste errors that introduce smart quotes (
"") or extra quotation marks into the URL.
This matters for several reasons. Malformed src attributes can cause the script to fail to load entirely, breaking functionality on your page. It also violates the HTML specification, which can lead to unpredictable behavior across different browsers as each parser tries to recover from the error differently.
To fix the issue, carefully inspect the <script> tag and ensure that:
- The
srcattribute value contains only a valid URL with no unescaped"characters. - The opening and closing quotes around the attribute value are properly balanced.
- Attributes are separated by whitespace — not jammed together.
- Boolean attributes like
asyncanddeferare written as bare keywords without values.
If you genuinely need a double quote character in a query string (which is rare), encode it as %22.
Examples
Incorrect — stray quotes between attributes
In this example, the async attribute is malformed as async"", which causes the parser to interpret the extra quotes as part of the src URL:
<script src="https://example.com/js/app.js?ver=3.1" async""></script>
Incorrect — doubled closing quote
Here, an extra " at the end of the src value creates an illegal character in the URL:
<script src="https://example.com/js/app.js?ver=3.1""></script>
Incorrect — smart quotes from copy-paste
Curly/smart quotes copied from a word processor are not valid HTML attribute delimiters and get treated as part of the URL:
<script src="https://example.com/js/app.js?ver=3.1"></script>
Correct — clean attributes with proper quoting
<script src="https://example.com/js/app.js?ver=3.1" async></script>
Correct — boolean attribute written as a bare keyword
Boolean attributes like async and defer don't need a value. Simply include the attribute name:
<script src="https://example.com/js/app.js?ver=3.1" defer></script>
While async="async" is technically valid per the spec, the cleanest and most common form is the bare attribute. Avoid empty-string patterns like async="" placed without proper spacing, as they can lead to the exact quoting errors described here.
Correct — full valid document
<!DOCTYPE html>
<html lang="en">
<head>
<title>Valid Script Example</title>
<script src="https://example.com/js/app.js?ver=3.1" async></script>
</head>
<body>
<p>Page content here.</p>
</body>
</html>
If you're generating <script> tags dynamically (through a CMS, template engine, or build tool), check the template source rather than just the rendered output. The stray quotes are often introduced by incorrect string concatenation or escaping logic in the server-side code.
Spaces in the src attribute of a <source> element are not valid URL characters and must be encoded or removed.
URLs follow strict syntax rules defined in RFC 3986. A space character is not permitted in any part of a URL path. When a file name or path contains spaces, you must replace each space with %20 (the percent-encoded form) or rename the file to avoid spaces altogether.
This applies to all elements that accept a URL, including <source>, <img>, <a>, <script>, and <link>. Browsers often handle spaces gracefully by encoding them automatically, but the HTML is still technically invalid and can cause issues in some contexts, such as when URLs are copied, shared, or processed by other tools.
The best practice is to avoid spaces in file names entirely. Use hyphens (-) or underscores (_) instead. If you can't rename the files, percent-encode the spaces.
Bad Example
<video controls>
<source src="videos/my cool video.mp4" type="video/mp4">
</video>
Good Example — Percent-Encoded Spaces
<video controls>
<source src="videos/my%20cool%20video.mp4" type="video/mp4">
</video>
Good Example — No Spaces in File Name
<video controls>
<source src="videos/my-cool-video.mp4" type="video/mp4">
</video>
When the W3C HTML Validator reports "Expected a slash," it means the URL parser encountered an unexpected character where a / should appear. URLs follow a strict syntax defined by the URL Living Standard. For scheme-based URLs, the format requires the scheme (like https:) to be immediately followed by // and then the authority (hostname). Any deviation — such as a space, a missing slash, or an encoded character in the wrong place — will make the URL invalid.
This matters for several reasons. Browsers may attempt to correct malformed URLs, but their behavior is inconsistent and unpredictable. A broken src attribute can cause images not to load, scripts to fail silently, or media elements to show fallback content. Screen readers and assistive technologies rely on valid URLs to provide meaningful information to users. Search engine crawlers may also fail to follow or index resources with malformed URLs.
Common causes of this error include:
- Accidental spaces inserted within the URL, especially between the scheme and the double slashes (e.g.,
https: //instead ofhttps://). - Missing slashes in the scheme (e.g.,
https:/example.comwith only one slash). - Copy-paste artifacts where invisible characters or line breaks get embedded in the URL string.
- Template or CMS issues where dynamic URL generation introduces unexpected characters.
To fix the issue, carefully inspect the src attribute value and ensure it forms a valid, complete URL with no stray characters. If the URL is generated dynamically, check the code that constructs it.
Examples
Incorrect: space between scheme and slashes
<img src="https: //example.com/photo.jpg" alt="A photo">
The space after https: breaks the URL. The validator expects a / immediately after the colon but finds a space instead.
Fixed: remove the space
<img src="https://example.com/photo.jpg" alt="A photo">
Incorrect: single slash instead of double slash
<script src="https:/cdn.example.com/app.js"></script>
The URL scheme requires // after https:, but only one / is present.
Fixed: use the correct double slash
<script src="https://cdn.example.com/app.js"></script>
Incorrect: line break embedded in the URL
Sometimes copy-pasting or template rendering introduces hidden line breaks:
<video src="https://
example.com/video.mp4">
</video>
Fixed: ensure the URL is on a single line with no breaks
<video src="https://example.com/video.mp4"></video>
Incorrect: protocol-relative URL with a missing slash
<img src="/example.com/logo.png" alt="Logo">
If the intent is a protocol-relative URL, it needs two slashes. With a single slash, this becomes an absolute path on the current domain rather than a reference to example.com.
Fixed: use two slashes for protocol-relative URLs
<img src="//example.com/logo.png" alt="Logo">
Tip: If you're having trouble spotting the problem, paste the URL into your browser's address bar to see if it resolves correctly, or use a text editor that reveals invisible characters (like zero-width spaces or non-breaking spaces) that may be hiding in the string.
The srcset attribute allows you to provide the browser with a set of image sources to choose from based on the user's viewport size or display density. Each entry in srcset is called an image candidate string and consists of a URL followed by an optional descriptor — either a width descriptor (like 300w) or a pixel density descriptor (like 2x).
The sizes attribute tells the browser what display size the image will occupy at various viewport widths, using media conditions and length values. The browser uses this size information together with the width descriptors in srcset to select the most appropriate image. This is why the HTML specification requires that when sizes is present, all srcset entries must use width descriptors — without them, the browser cannot perform the size-based selection that sizes is designed to enable.
This error typically appears in three situations:
- A
srcsetentry has no descriptor at all — the URL is listed without any accompanying width or density value. - A pixel density descriptor (
x) is used alongsidesizes— mixingsizeswithxdescriptors is invalid because the two mechanisms are mutually exclusive. - A typo or formatting issue — for example, writing
600pxinstead of600w, or placing a comma incorrectly.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly states that when
sizesis specified, all image candidates must use width descriptors. - Correct image selection: Without proper width descriptors, browsers cannot accurately determine which image to download. This may lead to unnecessarily large downloads on small screens or blurry images on large screens.
- Performance: Responsive images are a key performance optimization. A malformed
srcsetdefeats the purpose and can result in wasted bandwidth.
How to fix it
- Determine the intrinsic width (in pixels) of each image file listed in
srcset. - Append the width descriptor to each URL in the format
[width]w, where[width]is the image's actual pixel width. - Ensure no entries use
xdescriptors whensizesis present. If you need density descriptors, remove thesizesattribute entirely. - Make sure every entry has a descriptor — bare URLs without any descriptor are invalid when
sizesis used.
Examples
Missing width descriptor
This triggers the validation error because the srcset URL has no width descriptor:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg"
sizes="(max-width: 600px) 100vw, 600px"
alt="A sunset over the mountains"
>
Fixed by adding the width descriptor:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 600w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A sunset over the mountains"
>
Using pixel density descriptors with sizes
This is invalid because x descriptors cannot be combined with the sizes attribute:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 1x, /img/photo-2x.jpg 2x"
sizes="(max-width: 800px) 100vw, 800px"
alt="A sunset over the mountains"
>
Fixed by switching to width descriptors:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 800w, /img/photo-2x.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
alt="A sunset over the mountains"
>
Alternatively, if you only need density-based selection and don't need sizes, remove it:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 1x, /img/photo-2x.jpg 2x"
alt="A sunset over the mountains"
>
Multiple image sources with width descriptors
A complete responsive image setup with several sizes:
<img
src="/img/photo-800.jpg"
srcset="
/img/photo-400.jpg 400w,
/img/photo-800.jpg 800w,
/img/photo-1200.jpg 1200w
"
sizes="(max-width: 480px) 100vw, (max-width: 960px) 50vw, 800px"
alt="A sunset over the mountains"
>
Each URL is paired with a w descriptor that matches the image's intrinsic pixel width. The sizes attribute then tells the browser how wide the image will display at each breakpoint, allowing it to pick the best candidate.
The srcset attribute allows you to specify multiple image sources so the browser can choose the most appropriate one based on the user's device characteristics, such as screen resolution or viewport width. When you include a srcset attribute on an <img> element, the HTML specification requires it to contain one or more comma-separated image candidate strings. Each string consists of a URL followed by an optional descriptor — either a width descriptor (e.g., 200w) or a pixel density descriptor (e.g., 2x).
This validation error typically appears when:
- The
srcsetattribute is empty (srcset="") - The
srcsetattribute contains only whitespace (srcset=" ") - The value contains syntax errors such as missing URLs, invalid descriptors, or incorrect formatting
- A templating engine or CMS outputs the attribute with no value
This matters because browsers rely on the srcset value to select the best image to load. An empty or malformed srcset means the browser must fall back entirely to the src attribute, making the srcset attribute pointless. Additionally, invalid markup can cause unexpected behavior across different browsers and undermines standards compliance.
How to fix it
- Provide valid image candidate strings. Each entry needs a URL and optionally a width or pixel density descriptor, with entries separated by commas.
- Remove the attribute entirely if you don't have multiple image sources to offer. A plain
srcattribute is perfectly fine on its own. - Check dynamic output. If a CMS or templating system generates the
srcset, ensure it conditionally omits the attribute when no responsive image candidates are available, rather than outputting an empty attribute.
Examples
❌ Empty srcset attribute
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="">
This triggers the error because srcset is present but contains no image candidate strings.
❌ Malformed srcset value
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="1x, 2x">
This is invalid because each candidate string must include a URL. Descriptors alone are not valid entries.
✅ Using pixel density descriptors
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 1x,
/img/photo-800.jpg 2x
">
Each candidate string contains a URL followed by a pixel density descriptor (1x, 2x). The browser picks the best match for the user's display.
✅ Using width descriptors with sizes
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 400w,
/img/photo-800.jpg 800w,
/img/photo-1200.jpg 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
Width descriptors (e.g., 400w) tell the browser the intrinsic width of each image. The sizes attribute then tells the browser how large the image will be displayed at various viewport sizes, allowing it to calculate the best source to download.
✅ Removing srcset when not needed
<img src="/img/photo.jpg" alt="A sunset over the ocean">
If you only have a single image source, simply omit srcset altogether. The src attribute alone is valid and sufficient.
✅ Single candidate in srcset
<img
src="/img/photo.jpg"
alt="A sunset over the ocean"
srcset="/img/photo-highres.jpg 2x">
Even a single image candidate string is valid. Here, the browser will use the high-resolution image on 2x displays and fall back to src otherwise.
The srcset attribute allows you to provide multiple image sources so the browser can choose the most appropriate one based on the user's viewport size or screen density. There are two distinct modes for srcset:
- Width descriptor mode — each candidate specifies its intrinsic width using a
wdescriptor (e.g.,400w). This mode requires thesizesattribute so the browser knows how much space the image will occupy in the layout and can calculate which source to download. - Pixel density descriptor mode — each candidate specifies a pixel density using an
xdescriptor (e.g.,2x). This mode must not include asizesattribute.
When you include a sizes attribute but forget to add width descriptors to one or more srcset entries, the browser has incomplete information. The HTML specification explicitly states that if sizes is present, all image candidate strings must use width descriptors. An entry without a descriptor defaults to 1x (a pixel density descriptor), which conflicts with the width descriptor mode triggered by sizes. This mismatch causes the W3C validator to report the error.
Beyond validation, this matters for real-world performance. Responsive images are one of the most effective tools for reducing page weight on smaller screens. If the descriptors are missing or mismatched, browsers may download an image that is too large or too small, hurting both performance and visual quality.
How to fix it
You have two options depending on your use case:
Option 1: Add width descriptors to all srcset candidates
If you need the browser to select images based on viewport size (the most common responsive images pattern), keep the sizes attribute and ensure every srcset entry has a w descriptor that matches the image's intrinsic pixel width.
Option 2: Remove sizes and use pixel density descriptors
If you only need to serve higher-resolution images for high-DPI screens (e.g., Retina displays) and the image always renders at the same CSS size, remove the sizes attribute and use x descriptors instead.
Examples
❌ Incorrect: sizes present but srcset entry has no width descriptor
<img
src="photo-800.jpg"
srcset="photo-400.jpg, photo-800.jpg"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
Both srcset entries lack a width descriptor. Because sizes is present, the validator reports an error for each candidate.
✅ Correct: sizes present with width descriptors on every candidate
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
Each candidate now specifies its intrinsic width (400w and 800w), which tells the browser the actual pixel width of each source file. The browser combines this with the sizes value to pick the best match.
❌ Incorrect: mixing width descriptors and bare entries
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
The second candidate (photo-800.jpg) is missing its width descriptor. All candidates must have one when sizes is present — not just some of them.
✅ Correct: pixel density descriptors without sizes
<img
src="photo-800.jpg"
srcset="photo-800.jpg 1x, photo-1600.jpg 2x"
alt="A mountain landscape">
Here the sizes attribute is removed, and each srcset entry uses a pixel density descriptor (1x, 2x). This is valid and appropriate when the image always occupies the same CSS dimensions regardless of viewport width.
❌ Incorrect: using sizes with pixel density descriptors
<img
src="photo-800.jpg"
srcset="photo-800.jpg 1x, photo-1600.jpg 2x"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
The sizes attribute and x descriptors cannot be combined. Either switch to w descriptors or remove sizes.
Quick reference
| Pattern | srcset descriptor | sizes required? |
|---|---|---|
| Viewport-based selection | Width (w) | Yes |
| Density-based selection | Pixel density (x) | No — must be omitted |
Remember that the w value in srcset refers to the image file's intrinsic pixel width (e.g., an 800-pixel-wide image gets 800w), while values in sizes use CSS length units like px, vw, or em to describe how wide the image will render in the layout.
In URLs, percent-encoding is used to represent special or reserved characters. The format is a % sign followed by exactly two hexadecimal digits (0–9, A–F), such as %20 for a space or %3F for a question mark. When the browser encounters a % in a URL, it expects the next two characters to be valid hex digits. If they aren't — for example, % followed by a letter like G, a non-hex character, or nothing at all — the URL is considered malformed.
This issue specifically targets the srcset attribute on <source> elements (commonly used inside <picture> elements), where one or more image candidate URLs contain an invalid percent sequence. The most common causes are:
- A literal
%sign in a query parameter — e.g.,?quality=80%where%is meant literally but isn't encoded. - Truncated percent-encoding — e.g.,
%2instead of%2F, possibly from a copy-paste error or a broken URL-encoding function. - Double-encoding gone wrong — a URL that was partially encoded, leaving some
%characters in an ambiguous state.
This matters because browsers may handle malformed URLs inconsistently. Some browsers might try to recover gracefully, while others may fail to load the image entirely. Invalid URLs also break standards compliance, can cause issues with CDNs and caching layers, and make your markup unreliable across different environments.
How to fix it
- Find the offending
%character in yoursrcsetURL. - If the
%is meant literally (e.g., as part of a percentage value like80%), encode it as%25. - If the
%is part of an incomplete percent-encoding (e.g.,%2instead of%2F), correct it to the full three-character sequence. - Review your URL-generation logic — if URLs are built dynamically by a CMS, template engine, or server-side code, ensure proper encoding is applied before output.
Examples
Invalid: literal % in srcset URL
The % in quality=80% is not followed by two hex digits, so it's treated as a broken percent-encoding sequence.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Valid: % encoded as %25
Replacing the bare % with %25 produces a valid URL.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%25"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Invalid: truncated percent-encoding in a file path
Here, %2 is incomplete — it should be %2F (which decodes to /) or some other valid sequence.
<picture>
<source
srcset="https://example.com/images%2photo.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Valid: corrected percent-encoding
The sequence is now %2F, a properly formed encoding.
<picture>
<source
srcset="https://example.com/images%2Fphoto.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Invalid: unencoded % in srcset with multiple candidates
Every URL in the srcset list must be valid. Here, both candidates contain a bare %.
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75% 480w,
https://example.com/img.webp?w=800&q=75% 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Valid: all candidates properly encoded
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75%25 480w,
https://example.com/img.webp?w=800&q=75%25 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Note that in the corrected example, & is also encoded as & within the HTML attribute, which is required for valid markup when using ampersands in attribute values.
A srcset attribute on a <source> element inside a <picture> requires each image URL to be paired with a width descriptor (w) or a pixel density descriptor (x).
The srcset attribute differs from the src attribute. While src accepts a bare URL, srcset expects one or more image candidates, where each candidate is a URL followed by a space and a descriptor. When no descriptor is provided, the W3C validator rejects the value because it cannot determine how the browser should select the image.
A width descriptor like 600w tells the browser the intrinsic width of the image in pixels. A pixel density descriptor like 1x or 2x indicates the intended display density. If you only have a single image and don't need responsive selection, you can still satisfy the validator by appending a descriptor.
When <source> is used inside <picture>, the srcset attribute is required instead of src. This is defined in the HTML specification: the <source> element within <picture> does not support src.
Invalid example
<picture>
<source type="image/webp" srcset="/images/img01.webp">
<img src="/images/img01.jpg" alt="A sample image" width="600" height="400">
</picture>
Valid examples
Add a width descriptor to each entry in srcset:
<picture>
<source
type="image/webp"
srcset="/images/img01-600.webp 600w, /images/img01-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 600px">
<img src="/images/img01.jpg" alt="A sample image" width="600" height="400">
</picture>
If you have a single image and no responsive variants, use a pixel density descriptor:
<picture>
<source type="image/webp" srcset="/images/img01.webp 1x">
<img src="/images/img01.jpg" alt="A sample image" width="600" height="400">
</picture>
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