HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The min attribute defines the minimum acceptable value for form input types such as number, range, date, time, datetime-local, week, and month. When the browser or the W3C validator encounters min="", it attempts to parse the empty string as a floating point number and fails because the empty string is not a valid representation of any number according to the HTML specification's rules for parsing floating point numbers.
This issue commonly arises when templating engines or server-side code dynamically set the min attribute but output an empty value when no minimum is configured, or when developers add the attribute as a placeholder intending to fill it in later.
Why this matters
- Standards compliance: The HTML specification explicitly requires the
minattribute's value to be a valid floating point number (for numeric types) or a valid date/time string (for date/time types). An empty string satisfies neither requirement. - Unpredictable browser behavior: When browsers encounter an invalid
minvalue, they typically ignore the attribute entirely. This means your intended constraint silently disappears, potentially allowing users to submit out-of-range values. - Accessibility concerns: Assistive technologies may rely on
minandmaxto communicate valid input ranges to users. An invalid value can lead to confusing or missing guidance for screen reader users. - Form validation issues: Built-in browser validation using the Constraint Validation API depends on valid
minvalues. An empty string can cause the browser's native validation to behave inconsistently across different browsers.
How to fix it
You have two straightforward options:
- Provide a valid value: Set
minto the actual minimum number or date/time string you want to enforce. - Remove the attribute: If no minimum constraint is needed, simply omit the
minattribute. The input will then accept any value within its type's natural range.
If your min value comes from dynamic server-side or JavaScript logic, make sure the attribute is only rendered when a valid value is available, rather than outputting an empty string as a fallback.
Examples
❌ Invalid: empty string for min
<inputtype="number"min=""max="10">
The empty string "" is not a valid floating point number, so this triggers the validation error.
✅ Fixed: provide a valid number
<inputtype="number"min="0"max="10">
✅ Fixed: remove min if no minimum is needed
<inputtype="number"max="10">
❌ Invalid: empty min on a range input
<inputtype="range"min=""max="100"step="5">
✅ Fixed: valid min on a range input
<inputtype="range"min="0"max="100"step="5">
❌ Invalid: empty min on a date input
<inputtype="date"min=""max="2025-12-31">
For date inputs, min must be a valid date string in YYYY-MM-DD format — an empty string is equally invalid here.
✅ Fixed: valid min on a date input
<inputtype="date"min="2025-01-01"max="2025-12-31">
Handling dynamic values in templates
If you're using a templating language and the minimum value might not always exist, conditionally render the attribute rather than outputting an empty value. For example, in a generic template pseudocode:
<!-- Instead of always outputting the attribute: -->
<inputtype="number"min=""max="10">
<!-- Only include it when a value is available: -->
<inputtype="number"min="5"max="10">
In practice, use your templating engine's conditional logic (e.g., {% if min_value %}min="{{ min_value }}"{% endif %} in Jinja2, or similar constructs) to ensure min is only present when it holds a valid value.
The name attribute on <a> elements was historically used to create named anchors — fragment targets that could be linked to with href="#anchorName". In modern HTML (the WHATWG living standard), the name attribute on <a> is considered obsolete for this purpose. The id attribute is now the standard way to create fragment targets, and it can be placed on any element, not just <a> tags.
Regardless of whether you use name or id, the value must be a non-empty string. The W3C validator enforces this rule because an empty identifier serves no functional purpose — it cannot be referenced by a fragment link, it cannot be targeted by JavaScript, and it creates invalid markup. Browsers may silently ignore it, but it pollutes the DOM and signals a likely mistake in the code.
Empty name attributes often appear in content migrated from older CMS platforms or WYSIWYG editors that inserted placeholder anchors like <a name=""></a>. They can also result from templating systems where a variable intended to populate the attribute resolved to an empty string.
Why this matters
- Standards compliance: Both the WHATWG HTML living standard and the W3C HTML specification require that identifier-like attributes (
id,name) must not be empty strings. - Accessibility: Screen readers and assistive technologies may attempt to process named anchors. Empty identifiers create noise without providing any navigational value.
- Functionality: An empty
nameoridcannot be used as a fragment target, so the element is effectively useless as a link destination.
How to fix it
- Remove the element entirely if the empty anchor serves no purpose — this is the most common fix.
- Replace
namewithidand provide a meaningful, non-empty value if you need a fragment target. - Move the
idto a nearby semantic element instead of using a standalone empty<a>tag. For example, place theiddirectly on a heading, section, or paragraph. - Ensure uniqueness — every
idvalue in a document must be unique.
Examples
❌ Empty name attribute triggers the error
<aname=""></a>
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
❌ Empty name generated by a template
<aname=""></a>
<p>This anchor was meant to be a target but the value is missing.</p>
<ahref="#">Jump to section</a>
✅ Remove the empty anchor if it's unnecessary
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
✅ Use id on the target element directly
<h2id="introduction">Introduction</h2>
<p>Welcome to the guide.</p>
<!-- Link to the section from elsewhere -->
<ahref="#introduction">Go to Introduction</a>
✅ Use id on a standalone anchor if needed
If you need a precise anchor point that doesn't correspond to an existing element, use an <a> tag with a valid, non-empty id:
<aid="section-start"></a>
<p>This paragraph follows the anchor point.</p>
<ahref="#section-start">Jump to section start</a>
✅ Migrate legacy name to id
If your existing code uses the obsolete name attribute with a valid value, update it to use id instead:
<!-- Before (obsolete but was valid in HTML4) -->
<aname="contact"></a>
<!-- After (modern HTML) -->
<aid="contact"></a>
<!-- Even better: put the id on a semantic element -->
<h2id="contact">Contact Us</h2>
The HTML specification requires that if the name attribute is used on a <form> element, its value must be a non-empty string. An empty name="" attribute serves no practical purpose — it doesn't register the form in the document.forms named collection, and it can't be used as a valid reference in scripts. The W3C validator flags this as an error because it violates the content model defined in the WHATWG HTML Living Standard.
The name attribute on a form is primarily used to access the form programmatically through document.forms["formName"]. When the value is empty, this lookup mechanism doesn't work, so the attribute becomes meaningless. This is different from the id attribute, which also identifies elements but participates in fragment navigation and CSS targeting. The name attribute on <form> is specifically for the legacy document.forms named getter interface.
It's worth noting that the name attribute value on a form must not equal an empty string, and it should be unique among the form elements in the forms collection. While duplicate names won't cause a validation error, they can lead to unexpected behavior when accessing forms by name in JavaScript.
How to fix
You have two options:
- Remove the attribute entirely. If you're not referencing the form by name in JavaScript, the
nameattribute is unnecessary. You can useidinstead for CSS or JavaScript targeting. - Provide a meaningful, non-empty value. If you need to reference the form through
document.forms, give it a descriptive name that reflects its purpose.
Examples
❌ Invalid: empty name attribute
<formname="">
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email">
<buttontype="submit">Subscribe</button>
</form>
This triggers the validator error because the name attribute is present but has an empty string value.
✅ Fixed: attribute removed
<form>
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email">
<buttontype="submit">Subscribe</button>
</form>
If you don't need to reference the form by name, simply remove the attribute.
✅ Fixed: meaningful name provided
<formname="subscriptionForm">
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email">
<buttontype="submit">Subscribe</button>
</form>
With a non-empty name, you can now access the form in JavaScript using document.forms["subscriptionForm"] or document.forms.subscriptionForm.
✅ Alternative: using id instead
<formid="subscription-form">
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email">
<buttontype="submit">Subscribe</button>
</form>
In modern development, using id with document.getElementById() or document.querySelector() is often preferred over the name attribute for form identification. The name attribute on <form> is a legacy feature that remains valid but isn't required for most use cases.
The name attribute on an <input> element identifies the form control's data when the form is submitted. It acts as the key in the key-value pair sent to the server (e.g., email=user@example.com). When you set name="", the attribute is present but contains an empty string, which the HTML specification considers an invalid value. An empty name means the input's data will be excluded from the form's submission payload in most browsers, making it functionally useless for form processing.
This issue matters for several reasons:
- Form functionality: Inputs with empty names are typically omitted from form data, so the server never receives the user's input.
- Standards compliance: The HTML specification requires that if the
nameattribute is present, its value must not be empty. - JavaScript references: An empty
namemakes it difficult to reference the element using methods likedocument.getElementsByName()orFormData. - Accessibility: Screen readers and assistive technologies may use the
nameattribute to help identify form controls, and an empty value provides no useful information.
Note that the name attribute is not technically required on every <input> element — it's perfectly valid to omit it entirely. For example, inputs used purely for client-side JavaScript interactions without form submission don't need a name. The error specifically arises when the attribute is present but set to an empty string.
To fix the issue, either assign a meaningful name that describes the data the input collects, or remove the name attribute altogether if the input isn't part of a form submission.
Examples
❌ Empty name attribute triggers the error
<formaction="/submit"method="post">
<labelfor="email">Email:</label>
<inputtype="email"id="email"name="">
<buttontype="submit">Submit</button>
</form>
✅ Providing a meaningful name value
<formaction="/submit"method="post">
<labelfor="email">Email:</label>
<inputtype="email"id="email"name="email">
<buttontype="submit">Submit</button>
</form>
✅ Removing name when the input isn't submitted
If the input is only used for client-side interaction and doesn't need to be part of form data, simply omit the attribute:
<labelfor="search">Filter results:</label>
<inputtype="text"id="search">
❌ Multiple inputs with empty names
This pattern sometimes appears when inputs are generated dynamically with placeholder attributes:
<formaction="/register"method="post">
<inputtype="text"name="">
<inputtype="password"name="">
<buttontype="submit">Register</button>
</form>
✅ Each input gets a descriptive name
<formaction="/register"method="post">
<labelfor="username">Username:</label>
<inputtype="text"id="username"name="username">
<labelfor="password">Password:</label>
<inputtype="password"id="password"name="password">
<buttontype="submit">Register</button>
</form>
The poster attribute tells the browser which image to display as a preview frame before the user plays the video. According to the WHATWG HTML Living Standard, the poster attribute's value must be a valid non-empty URL potentially surrounded by spaces. When you include poster="", the attribute is present but its value is an empty string, which is not a valid URL — triggering the W3C validator error: Bad value "" for attribute "poster" on element "video": Must be non-empty.
This issue commonly occurs in a few scenarios:
- Template or CMS output where the poster URL is dynamically generated but the variable resolves to an empty string (e.g.,
poster="<?= $posterUrl ?>"when$posterUrlis empty). - JavaScript frameworks that bind a value to the
posterattribute, but the bound variable isnull,undefined, or an empty string. - Manual editing where a developer adds the attribute as a placeholder intending to fill it in later.
Why This Matters
While most browsers will gracefully handle an empty poster attribute by simply not displaying a poster image, there are good reasons to fix this:
- Standards compliance: An empty
postervalue violates the HTML specification. Valid markup ensures predictable behavior across all browsers and devices. - Unnecessary network requests: Some browsers may attempt to resolve the empty string as a relative URL, potentially triggering a failed HTTP request to the current page's URL. This wastes bandwidth and clutters developer tools and server logs with spurious errors.
- Accessibility: Screen readers and assistive technologies may interpret the empty attribute inconsistently, leading to confusing announcements for users.
How to Fix It
You have two straightforward options:
- Provide a valid image URL — If you want a poster frame, set the value to a real image path.
- Remove the attribute entirely — If you don't need a poster image, simply omit
poster. The browser will either show nothing or display the first frame of the video once enough data has loaded, depending on thepreloadattribute setting.
If the empty value comes from dynamic code, add a conditional check so the poster attribute is only rendered when a URL is actually available.
Examples
❌ Invalid: Empty poster attribute
<videocontrolsposter="">
<sourcesrc="movie.mp4"type="video/mp4">
Your browser does not support the video tag.
</video>
This triggers the validation error because poster is present but has no value.
✅ Fixed: Poster attribute with a valid URL
<videocontrolsposter="thumbnail.jpg">
<sourcesrc="movie.mp4"type="video/mp4">
Your browser does not support the video tag.
</video>
The poster attribute now points to a valid image file that the browser will display before playback begins.
✅ Fixed: Poster attribute removed entirely
<videocontrols>
<sourcesrc="movie.mp4"type="video/mp4">
Your browser does not support the video tag.
</video>
When no poster image is needed, omitting the attribute is the cleanest solution.
✅ Fixed: Conditional rendering in a template
If you're using a templating language or framework, conditionally include the attribute only when a value exists. Here's a conceptual example using a server-side template:
<!-- Pseudo-template syntax: only render poster when posterUrl is not empty -->
<videocontrols{{#ifposterUrl}}poster="{{posterUrl}}"{{/if}}>
<sourcesrc="movie.mp4"type="video/mp4">
Your browser does not support the video tag.
</video>
This pattern prevents the empty poster="" from ever reaching the rendered HTML, keeping your output valid regardless of whether a poster URL is available.
An empty string is not a valid value for the role attribute. Either assign a recognized WAI-ARIA role or remove the attribute entirely.
The role attribute tells browsers and assistive technologies what purpose an element serves in the page. Every value must be a valid ARIA role token defined in the WAI-ARIA specification, such as banner, navigation, alert, dialog, or presentation. When set to an empty string (""), the attribute has no meaningful value, and the W3C validator rejects it.
Some templating engines and JavaScript frameworks conditionally set the role attribute but output an empty string when no role applies. In those cases, the fix is to either omit the attribute altogether or conditionally render it only when a valid role is available.
If the element does not need an explicit ARIA role, remove the attribute. Native HTML elements like <nav>, <header>, <main>, and <aside> already carry implicit roles, so adding role to them is usually unnecessary.
HTML examples
Invalid: empty role attribute
<divrole="">
<p>Some content</p>
</div>
Fixed: remove the attribute or assign a valid role
Remove the attribute when no specific role is needed:
<div>
<p>Some content</p>
</div>
Or assign a valid ARIA role:
<divrole="alert">
<p>Something went wrong.</p>
</div>
The <textarea> element represents a multi-line plain-text editing control, commonly used for comments, feedback forms, and other free-form text input. The rows attribute specifies the number of visible text lines the textarea should display. According to the HTML specification, when the rows attribute is present, its value must be a valid positive integer — meaning a string of one or more digits representing a number greater than zero (e.g., 1, 5, 20). An empty string ("") does not meet this requirement.
This issue typically occurs when a template engine, CMS, or JavaScript framework dynamically sets the rows attribute but outputs an empty value instead of a number, or when a developer adds the attribute as a placeholder intending to fill it in later.
Why this matters
- Standards compliance: The HTML specification explicitly requires
rowsto be a positive integer when present. An empty string violates this rule. - Unpredictable rendering: Browsers fall back to a default value (typically
2) when they encounter an invalidrowsvalue, but this behavior isn't guaranteed to be consistent across all browsers and versions. - Maintainability: Invalid attributes can mask bugs in dynamic code that was supposed to provide a real value.
How to fix it
You have two options:
- Set a valid positive integer: Replace the empty string with a number like
4,5, or whatever suits your design. - Remove the attribute: If you don't need to control the number of visible rows (or prefer to handle sizing with CSS), simply omit the
rowsattribute. The browser will use its default.
If the value is generated dynamically, ensure your code has a fallback so it never outputs an empty string. You can also control the textarea's height using CSS (height or min-height) instead of relying on the rows attribute.
Examples
❌ Invalid: empty string for rows
<textareaname="comments"rows=""cols="25">
</textarea>
This triggers the error because "" is not a valid positive integer.
✅ Fixed: providing a valid positive integer
<textareaname="comments"rows="5"cols="25">
</textarea>
Setting rows="5" tells the browser to display five visible lines of text.
✅ Fixed: removing the attribute entirely
<textareaname="comments"cols="25">
</textarea>
When rows is omitted, the browser uses its default (typically 2 rows). This is perfectly valid.
✅ Alternative: using CSS for sizing
<textareaname="comments"style="height:10em;width:25ch;">
</textarea>
If you need precise control over the textarea's dimensions, CSS properties like height, min-height, and width give you more flexibility than the rows and cols attributes. In this case, you can safely leave both attributes off.
The sizes attribute on an img element contains a malformed CSS length value, such as a missing digit, an invalid unit, or a typo in a media condition.
The sizes attribute tells the browser how wide an image will be displayed at different viewport sizes, so it can pick the best candidate from the srcset list before the page layout is calculated. Each entry in sizes is either a media condition paired with a CSS length, or a bare default length. The values must be valid CSS lengths like 100vw, 50vw, (max-width: 600px) 100vw, or use calc() expressions.
Common causes of this error:
- Writing
xdescriptors (like1x,2x) insidesizesinstead of insidesrcset. - Using pixel density values such as
100was a display size (that is asrcsetwidth descriptor, not a CSS length). - Omitting a space between the media condition and the length value.
- Using invalid CSS units or misspelling a unit (e.g.,
100 vwwith a space, orpx100with the unit before the number).
The sizes attribute only accepts valid CSS <length> values such as px, em, rem, vw, vh, and calc() expressions. Width descriptors (w) and pixel density descriptors (x) belong exclusively in srcset.
Incorrect example
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100w, 50vw"
alt="A mountain lake">
Here 100w is not a valid CSS length. The browser expects something like 100vw or 600px.
Fixed example
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A mountain lake">
Each value in sizes is now a valid CSS length: 100vw means the image will span the full viewport width on small screens, and 50vw is the default for larger viewports.
The sizes attribute on an img element contains a value that is missing a CSS unit (such as px, vw, or em).
The sizes attribute tells the browser how wide an image will be displayed at a given viewport condition. Each entry in the sizes list must be a valid CSS length. A bare number like 300 is not a valid CSS length — it must include a unit, such as 300px or 50vw.
A common mistake is writing something like sizes="(max-width: 600px) 300, 600" instead of sizes="(max-width: 600px) 300px, 600px". Another frequent error is omitting the unit on the default (last) value.
The sizes attribute accepts a comma-separated list of source size entries. Each entry except the last consists of a media condition followed by a length. The last entry is just a length and acts as the default. Every length value must have a CSS unit.
Invalid example
<img
src="photo.jpg"
srcset="photo-300.jpg 300w, photo-600.jpg 600w"
sizes="(max-width: 600px) 300, 600"
alt="A photo">
Both 300 and 600 lack units, so the validator rejects them.
Valid example
<img
src="photo.jpg"
srcset="photo-300.jpg 300w, photo-600.jpg 600w"
sizes="(max-width: 600px) 300px, 600px"
alt="A photo">
You can also use viewport-relative units like vw:
<img
src="photo.jpg"
srcset="photo-300.jpg 300w, photo-600.jpg 600w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A photo">
Check every length in your sizes attribute and make sure each one ends with a valid CSS unit. The calc() function is also allowed, for example calc(100vw - 2rem).
The sizes attribute works together with the srcset attribute to help the browser choose the most appropriate image source before the page layout is calculated. It describes how wide the image will be displayed under various viewport conditions, allowing the browser to pick an optimally sized image from the candidates listed in srcset. According to the HTML specification, the value of sizes must be a valid source size list — a comma-separated set of media conditions paired with lengths, with a default length at the end. An empty string ("") does not satisfy this requirement and is therefore invalid.
When the browser encounters an empty sizes attribute, it cannot determine the intended display width of the image. This defeats the purpose of responsive images and may cause the browser to fall back to a default behavior (typically assuming 100vw), which could result in downloading an unnecessarily large image. Beyond the functional issue, an empty attribute signals a code quality problem — it often means a template is outputting the attribute even when no value has been configured.
Why this matters
- Standards compliance: The HTML specification explicitly requires
sizesto be a non-empty, valid source size list when present. An empty value is a parse error. - Performance: Without a proper
sizesvalue, the browser cannot make an informed decision about whichsrcsetcandidate to download. This can lead to wasted bandwidth and slower page loads, especially on mobile devices. - Code quality: Empty attributes clutter your markup and suggest missing configuration, making the code harder to maintain.
How to fix it
- Provide a valid
sizesvalue that describes how wide the image will actually render at different viewport widths. - Remove
sizesentirely if you are not using width descriptors (w) insrcset. Whensrcsetuses pixel density descriptors (1x,2x), thesizesattribute is not needed. - Remove both
sizesandsrcsetif you don't need responsive image selection at all.
Examples
❌ Empty sizes attribute
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-large.jpg 1200w"
sizes=""
alt="A mountain landscape">
The empty sizes="" triggers the validation error.
✅ Valid sizes with a single default value
If the image always takes up the full viewport width:
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-large.jpg 1200w"
sizes="100vw"
alt="A mountain landscape">
✅ Valid sizes with media conditions
If the image displays at different widths depending on the viewport:
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-medium.jpg 800w, photo-large.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="A mountain landscape">
This tells the browser: use 100vw on viewports up to 600px, 50vw up to 1024px, and 33vw otherwise.
✅ Removing sizes when using density descriptors
When srcset uses density descriptors instead of width descriptors, sizes is not applicable and should be omitted:
<img
src="photo.jpg"
srcset="photo.jpg 1x, photo-2x.jpg 2x"
alt="A mountain landscape">
✅ Removing both attributes when not needed
If responsive image selection isn't required, simply use a standard <img>:
<imgsrc="photo.jpg"alt="A mountain landscape">
Common template fix
If your CMS or templating system conditionally outputs these attributes, ensure the sizes attribute is only rendered when it has a value. For example, in a template:
<!-- Before (always outputs sizes, even when empty) -->
<imgsrc="photo.jpg"srcset="{{srcset}}"sizes="{{sizes}}"alt="{{alt}}">
<!-- After (only outputs sizes when it has a value) -->
<imgsrc="photo.jpg"srcset="{{srcset}}"{{#ifsizes}}sizes="{{sizes}}"{{/if}} alt="{{alt}}">
Square brackets ([ and ]) are reserved characters under RFC 3986, the standard that defines URI syntax. They are only permitted in the host component of a URL (specifically for IPv6 addresses) and are illegal elsewhere, including the query string. While most browsers are lenient and will load an <iframe> even when the src contains raw brackets, the W3C HTML Validator correctly flags this as an invalid URL value.
This pattern surfaces frequently when working with frameworks or APIs that use bracket notation to represent arrays or nested objects in query parameters — for example, filters[category]=news or items[0]=apple. These URLs work in a browser's address bar because browsers silently fix malformed URLs, but the raw HTML itself is technically non-conforming.
Why it matters
- Standards compliance: A valid HTML document requires all attribute values that expect URLs to contain properly encoded URIs. Raw brackets violate this requirement.
- Interoperability: While mainstream browsers handle this gracefully, other HTML consumers — such as screen readers, web scrapers, embedded webview components, or email clients — may not be as forgiving.
- Maintainability: Properly encoded URLs are unambiguous and reduce the risk of subtle parsing bugs, especially when URLs are constructed dynamically or passed through multiple layers of processing.
How to fix it
There are two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. The server will decode them back to brackets automatically, so functionality is preserved.Use alternative parameter naming. If you control the server, switch to a naming convention that avoids brackets altogether, such as dot notation (
filters.category) or underscores (filters_category). This keeps the URL valid without any encoding.
If you're generating the URL dynamically in JavaScript, you can use encodeURIComponent() on individual parameter keys and values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
Incorrect — raw brackets in the query string
<iframesrc="https://example.com/embed?filters[category]=news&filters[tags]=web"></iframe>
The [ and ] characters in the query string make this an invalid URL value, triggering the validator error.
Fixed — percent-encoded brackets
<iframesrc="https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web"></iframe>
Replacing [ with %5B and ] with %5D produces a valid URL. The server receives the same parameter names after decoding.
Fixed — alternative parameter naming
<iframesrc="https://example.com/embed?filters.category=news&filters.tags=web"></iframe>
If the server supports it, using dot notation eliminates the need for brackets entirely, keeping the URL clean and valid.
Generating encoded URLs in JavaScript
<iframeid="content-frame"></iframe>
<script>
consturl=newURL("https://example.com/embed");
url.searchParams.set("filters[category]","news");
url.searchParams.set("filters[tags]","web");
document.getElementById("content-frame").src=url.href;
// Result: https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web
</script>
The URLSearchParams API automatically percent-encodes reserved characters, so you can write the parameter names naturally and let the browser produce a valid URL.
The src attribute on an <iframe> tells the browser which document to load and display within the embedded frame. When you leave this attribute empty (src=""), the W3C HTML Validator reports an error because the HTML specification requires the value to be a valid, non-empty URL. An empty string doesn't resolve to a meaningful resource.
This is more than a cosmetic validation issue. When a browser encounters src="", it typically interprets the empty string as a relative URL pointing to the current page, which causes the browser to re-fetch and embed the current document inside itself. This leads to unnecessary network requests, potential performance degradation, and unexpected rendering behavior. In some cases it can even cause infinite nesting loops or break page functionality.
From an accessibility standpoint, an empty <iframe> with no valid source provides no meaningful content to assistive technologies. Screen readers may announce the frame without being able to describe its purpose, creating a confusing experience for users.
How to fix it
- Provide a valid URL: Set the
srcattribute to the actual URL of the content you want to embed. - Use
about:blankfor intentionally empty frames: If you need an<iframe>in the DOM but don't have content to load yet (e.g., you plan to populate it later via JavaScript), usesrc="about:blank". This is a valid URL that loads a blank page without triggering extra requests. - Remove the element: If the
<iframe>isn't needed, remove it from the markup entirely. You can dynamically create and insert it with JavaScript when you have content to load. - Use
srcdocinstead: If you want to embed inline HTML rather than loading an external URL, use thesrcdocattribute, which accepts an HTML string directly.
Examples
❌ Empty src attribute
<iframesrc=""></iframe>
This triggers the validation error because the src value is an empty string.
❌ src attribute with only whitespace
<iframesrc=""></iframe>
Whitespace-only values are also invalid URLs and will produce the same error.
✅ Valid URL in src
<iframesrc="https://example.com/map.html"title="Location map"></iframe>
A fully qualified URL is the most straightforward fix. Note the title attribute, which is recommended for accessibility so that assistive technologies can describe the frame's purpose.
✅ Using about:blank for a placeholder frame
<iframesrc="about:blank"title="Dynamic content area"></iframe>
This is a valid approach when you need the <iframe> in the DOM but plan to set its content later with JavaScript using contentDocument.write() or by updating the src attribute dynamically.
✅ Using srcdoc for inline content
<iframesrcdoc="<p>Hello, embedded world!</p>"title="Inline content"></iframe>
The srcdoc attribute lets you embed HTML directly without needing an external URL. When srcdoc is present, it takes priority over src.
✅ Dynamically creating the iframe with JavaScript
<divid="video-container"></div>
<script>
constiframe=document.createElement("iframe");
iframe.src="https://example.com/video.html";
iframe.title="Video player";
document.getElementById("video-container").appendChild(iframe);
</script>
If the source URL isn't available at page load, creating the <iframe> dynamically avoids having an empty src in your HTML altogether.
A data: URI embeds resource content directly in a URL rather than pointing to an external file. It follows the format data:[<mediatype>][;base64],<data>. RFC 2397, which governs this scheme, explicitly states that fragment identifiers (the # character followed by a fragment name) are not valid in data: URIs.
This issue most commonly arises when developers try to reference a specific element inside an embedded SVG — for example, a particular <symbol> or element with an id — by appending a fragment like #icon-name to a data: URI. While fragments work in standard URLs (e.g., icons.svg#home), the data: URI scheme simply doesn't support them.
Why It Matters
- Standards compliance: Browsers may handle invalid
data:URIs inconsistently. Some may silently ignore the fragment, while others may fail to render the image entirely. - Portability: Code that relies on non-standard behavior in one browser may break in another or in a future update.
- Accessibility and tooling: Validators, linters, and assistive technologies expect well-formed URIs. An invalid URI can cause unexpected issues down the chain.
How to Fix It
You have several options depending on your use case:
- Remove the fragment from the
data:URI. If the embedded content is a complete, self-contained image, it doesn't need a fragment reference. - Inline the SVG directly into the HTML document. This lets you reference internal
idvalues with standard fragment syntax using<use>elements. - Use an external file instead of a
data:URI. Standard URLs likeicons.svg#homefully support fragment identifiers. - Encode the full, standalone SVG into the
data:URI so that it contains only the content you need, eliminating the need for a fragment reference.
Examples
❌ Incorrect: Fragment in a data: URI
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Csymbol id='icon'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/symbol%3E%3C/svg%3E#icon"
alt="Icon">
The #icon fragment at the end of the data: URI violates RFC 2397 and triggers the validation error.
✅ Correct: Self-contained SVG in a data: URI (no fragment)
Embed only the content you need directly, without wrapping it in a <symbol> or referencing a fragment:
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/svg%3E"
alt="Icon">
✅ Correct: External SVG file with a fragment
Move the SVG to a separate file and reference the specific symbol using a standard URL fragment:
<imgsrc="icons.svg#icon"alt="Icon">
✅ Correct: Inline SVG with <use> referencing an internal id
If you need to reference individual symbols from a sprite, inline the SVG and use fragment references within the same document:
<svgxmlns="http://www.w3.org/2000/svg"hidden>
<symbolid="icon-home"viewBox="0 0 20 20">
<pathd="M10 2 L2 10 L4 10 L4 18 L16 18 L16 10 L18 10 Z"/>
</symbol>
</svg>
<svgwidth="20"height="20"aria-hidden="true">
<usehref="#icon-home"></use>
</svg>
This approach gives you full control over individual icons without needing data: URIs at all, and it's the most flexible option for icon systems.
A space character in the fragment portion of a URL (the part after #) is not allowed in the src attribute of an img element.
URLs follow the syntax defined in RFC 3986, which does not permit literal spaces anywhere in the URI, including the fragment identifier. Browsers may silently handle spaces by percent-encoding them, but the raw HTML remains invalid. The W3C validator flags this because the src value must be a valid URL before the browser ever processes it.
To fix the issue, replace each space in the URL with %20, which is the percent-encoded form of a space character. This applies to all parts of the URL: the path, query string, and fragment.
A common cause is copying a URL from a browser address bar or a document system where filenames or anchors contain spaces. Another cause is linking to a named anchor (id) on a page where the id value itself contains a space, which is also invalid HTML — id values must not contain spaces.
HTML examples
Invalid: space in fragment
<imgsrc="diagram.svg#my section"alt="Diagram of section layout">
Valid: percent-encoded space
<imgsrc="diagram.svg#my%20section"alt="Diagram of section layout">
If you control the target resource, a better fix is to remove the space from the fragment identifier entirely:
<imgsrc="diagram.svg#my-section"alt="Diagram of section layout">
Using hyphens or underscores instead of spaces in fragment identifiers and file names avoids this class of problem altogether.
The URL specification (defined by WHATWG and RFC 3986) restricts which characters can appear unencoded in different parts of a URL. Square brackets are reserved characters that have a specific meaning in URLs — they are only permitted in the host portion (to wrap IPv6 addresses like [::1]). In the query string, they must be percent-encoded.
This issue commonly appears when frameworks (especially PHP, Ruby on Rails, or JavaScript libraries) generate URLs with array-style query parameters like ?filter[color]=red. While many browsers and servers are lenient and will interpret these URLs correctly, they are technically invalid according to the URL standard. The W3C validator enforces this rule strictly.
Beyond standards compliance, using unencoded square brackets can cause problems in practice. Some HTTP clients, proxies, or caching layers may reject or mangle URLs containing raw brackets. Percent-encoding these characters ensures your URLs are universally safe and interoperable across all systems.
How to fix it
You have two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. This preserves the parameter structure that your server or framework expects, while making the URL valid.Restructure the query parameters. If your backend allows it, use flat parameter names with dot notation, underscores, or dashes instead of bracket syntax. For example, change
size[width]tosize_widthorsize.width.
If you're generating URLs in JavaScript, the built-in encodeURI() function does not encode square brackets. Use encodeURIComponent() on individual parameter names or values, or manually replace [ and ] after constructing the URL.
Examples
❌ Invalid: unencoded square brackets in query string
<imgsrc="/images/photo.jpg?size[width]=300&size[height]=200"alt="A sample photo">
The literal [ and ] characters in the query string trigger the validation error.
✅ Fixed: percent-encoded brackets
<imgsrc="/images/photo.jpg?size%5Bwidth%5D=300&size%5Bheight%5D=200"alt="A sample photo">
Replacing [ with %5B and ] with %5D makes the URL valid. Most servers and frameworks will decode these automatically and interpret the parameters the same way.
✅ Fixed: flat parameter names without brackets
<imgsrc="/images/photo.jpg?size_width=300&size_height=200"alt="A sample photo">
If you control the server-side logic, you can avoid brackets altogether by using a flat naming convention for your parameters.
❌ Invalid: brackets in more complex query strings
<img
src="/api/image?filters[type]=jpeg&filters[quality]=80&crop[x]=10&crop[y]=20"
alt="Processed image">
✅ Fixed: all brackets encoded
<img
src="/api/image?filters%5Btype%5D=jpeg&filters%5Bquality%5D=80&crop%5Bx%5D=10&crop%5By%5D=20"
alt="Processed image">
Encoding in JavaScript
If you build image URLs dynamically, handle the encoding in your code:
// Manual replacement approach
consturl="/images/photo.jpg?size[width]=300&size[height]=200";
constsafeUrl=url.replace(/\[/g,"%5B").replace(/\]/g,"%5D");
img.src=safeUrl;
// Using URLSearchParams (automatically encodes brackets)
constparams=newURLSearchParams();
params.set("size[width]","300");
params.set("size[height]","200");
img.src=`/images/photo.jpg?${params.toString()}`;
Both approaches produce a valid, properly encoded URL that will pass W3C validation.
The W3C HTML validator raises this error when the src attribute of an <img> element contains characters that are not permitted in a valid URI. The most common culprit is the < character, but other characters like >, {, }, |, \, ^, and backticks are also illegal in URIs according to RFC 3986.
This issue typically occurs in a few common scenarios:
- Template syntax left unresolved: Server-side or client-side template tags (e.g.,
<%= imageUrl %>,{{ image.src }}) appear literally in the HTML output instead of being processed into actual URLs. - Copy-paste errors: HTML markup or angle brackets accidentally end up inside a
srcvalue. - Malformed dynamic URLs: JavaScript or server-side code incorrectly constructs a URL that includes raw HTML or special characters.
This matters because browsers may fail to load the image or interpret the URL unpredictably. Invalid URIs can also cause issues with screen readers and assistive technologies that try to resolve the src to provide context about the image. Keeping your markup standards-compliant ensures consistent behavior across all browsers and environments.
How to fix it
- Check for unprocessed template tags. If you see template syntax like
<%,{{, or similar in the rendered HTML, ensure your templating engine is running correctly and outputting the resolved URL. - Use valid, well-formed URLs. The
srcvalue should be a properly formatted absolute or relative URL. - Percent-encode special characters. If a special character is genuinely part of the URL (which is rare for
<), encode it:<becomes%3C,>becomes%3E, and so on. - Inspect your generated HTML. View the page source in your browser to confirm the actual output, rather than relying on what your code looks like before processing.
Examples
Incorrect — template syntax in src
The template tag was not processed, leaving a < character in the src attribute:
<imgsrc="<%= user.avatar %>"alt="User avatar">
Incorrect — HTML accidentally inside src
Angle brackets from stray markup ended up in the URL:
<imgsrc="images/<thumbnail>/photo.jpg"alt="Photo">
Correct — a valid relative URL
<imgsrc="images/photo.jpg"alt="Photo">
Correct — a valid absolute URL
<imgsrc="https://example.com/images/photo.jpg"alt="Photo">
Correct — special characters percent-encoded
If the URL genuinely requires characters that are not allowed in a URI, percent-encode them:
<imgsrc="https://example.com/search?q=a%3Cb"alt="Search result">
In this case, %3C represents the < character in the query string, making the URI valid.
The HTML specification requires the src attribute of an <img> element to be a valid, non-empty URL. When you set src="", the browser has no resource to fetch, but many browsers will still attempt to make a request — often resolving the empty string relative to the current page URL. This means the browser may re-request the current HTML document as if it were an image, wasting bandwidth and potentially causing unexpected server-side behavior.
Beyond the technical waste, an empty src is problematic for accessibility. Screen readers rely on the <img> element to convey meaningful content. An image with no source provides no value and can confuse assistive technology users. Search engine crawlers may also flag this as a broken resource, negatively affecting SEO.
This issue commonly arises in a few scenarios:
- Placeholder images — developers leave
srcempty intending to set it later via JavaScript. - Template rendering — a server-side template or frontend framework outputs an
<img>tag before the image URL is available. - Lazy loading implementations — the real source is stored in a
data-srcattribute whilesrcis left empty.
How to fix it
The simplest fix is to provide a valid image URL in the src attribute. If the image source isn't available yet, consider these alternatives:
- Don't render the
<img>element at all until a valid source is available. - Use a small placeholder image (such as a transparent 1×1 pixel GIF or a lightweight SVG) as a temporary
src. - Use native lazy loading with
loading="lazy"and a realsrc, letting the browser handle deferred loading instead of relying on an emptysrc.
Examples
❌ Bad: empty src attribute
<imgsrc=""alt="Profile photo">
This triggers the validation error because the src value is an empty string.
❌ Bad: src with only whitespace
<imgsrc=""alt="Profile photo">
Whitespace-only values are also considered invalid and will produce a similar error.
✅ Good: valid image path
<imgsrc="photo.jpg"alt="Profile photo">
✅ Good: placeholder image for lazy loading
If you're implementing lazy loading and need a lightweight placeholder, use a small inline data URI or a real placeholder file:
<img
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
data-src="photo.jpg"
alt="Profile photo">
✅ Good: native lazy loading with a real src
Modern browsers support the loading attribute, eliminating the need for an empty src workaround:
<imgsrc="photo.jpg"alt="Profile photo"loading="lazy">
✅ Good: conditionally render the element
If the image URL might not be available, avoid rendering the <img> tag entirely. For example, in a template:
<!-- Only include the img element when a source exists -->
<imgsrc="photo.jpg"alt="Profile photo">
In frameworks like React, Vue, or server-side templating engines, use conditional logic to skip the <img> element when the URL is empty rather than outputting a tag with an empty src.
The src attribute on a <script> element tells the browser where to fetch an external JavaScript file. According to the HTML specification, when the src attribute is present, its value must be a valid non-empty URL. An empty string does not qualify as a valid URL, so the validator flags it as an error.
This issue typically arises in a few common scenarios:
- Templating or CMS placeholders — A template engine or content management system outputs an empty
srcwhen no script URL is configured. - Dynamic JavaScript — Client-side code is intended to set the
srclater, but the initial HTML ships with an empty value. - Copy-paste mistakes — The attribute was added in anticipation of a script file that was never specified.
Beyond failing validation, an empty src causes real problems. Most browsers interpret an empty src as a relative URL that resolves to the current page's URL. This means the browser will make an additional HTTP request to re-fetch the current HTML document and attempt to parse it as JavaScript, wasting bandwidth, slowing down page load, and potentially generating console errors. It can also cause unexpected side effects in server logs and analytics.
How to Fix It
Choose the approach that matches your intent:
- Provide a valid URL — If you need an external script, set
srcto the correct file path or full URL. - Use an inline script — If your JavaScript is written directly in the HTML, remove the
srcattribute entirely. Note that whensrcis present, browsers ignore any content between the opening and closing<script>tags. - Remove the element — If the script isn't needed, remove the
<script>element altogether.
Also keep in mind:
- Ensure the file path is correct relative to the HTML file's location.
- If using an absolute URL, verify it is accessible and returns JavaScript content.
- If a script should only be loaded conditionally, handle the condition in your server-side or build logic rather than outputting an empty
src.
Examples
❌ Invalid: Empty src attribute
<scriptsrc=""></script>
This triggers the validation error because the src value is an empty string.
❌ Invalid: Whitespace-only src attribute
<scriptsrc=""></script>
A value containing only whitespace is also not a valid URL and will produce the same error.
✅ Fixed: Valid external script
<scriptsrc="js/app.js"></script>
The src attribute contains a valid relative URL pointing to an actual JavaScript file.
✅ Fixed: Valid external script with a full URL
<scriptsrc="https://example.com/js/library.min.js"></script>
✅ Fixed: Inline script without src
If you want to write JavaScript directly in your HTML, omit the src attribute:
<script>
console.log("This is an inline script.");
</script>
✅ Fixed: Conditionally omitting the element
If the script URL comes from a template variable that might be empty, handle it at the template level so the <script> element is only rendered when a URL is available. For example, in a templating language:
<!-- Pseudocode — only output the tag when the URL exists -->
<!-- {% if script_url %} -->
<scriptsrc="analytics.js"></script>
<!-- {% endif %} -->
This prevents the empty src from ever reaching the browser.
The <source> element is used inside <video>, <audio>, and <picture> elements to specify one or more media resources for the browser to choose from. Its src attribute is defined as a valid non-empty URL, meaning it must resolve to an actual file path or web address. An empty string does not satisfy this requirement and violates the HTML specification.
Why this is a problem
Standards compliance: The WHATWG HTML living standard explicitly requires the src attribute on <source> to be a non-empty, valid URL. An empty value makes the document invalid HTML.
Unexpected browser behavior: When a browser encounters an empty src, it may attempt to resolve it relative to the current page URL. This can trigger an unnecessary HTTP request back to the current page, resulting in wasted bandwidth, unexpected server load, or confusing errors in your network logs.
Broken media playback: An empty src means the browser has no media file to load. If the <source> element is the only one provided, the media element will fail to play entirely — often without a clear indication to the user of what went wrong.
Accessibility concerns: Screen readers and assistive technologies rely on well-formed HTML. Invalid markup can lead to unpredictable behavior or missed content announcements for users who depend on these tools.
How to fix it
- Provide a valid URL — Set the
srcattribute to the correct path or URL of your media file. - Remove the element — If the media source is not yet available or is being set dynamically via JavaScript, remove the
<source>element from the HTML entirely and add it later through script when the URL is known. - Check for template or CMS issues — This error often appears when a CMS or templating engine outputs a
<source>tag with an empty variable. Ensure your template conditionally renders the element only when a valid URL exists.
Examples
Incorrect: empty src on <source> in a video
<videocontrols>
<sourcesrc=""type="video/mp4">
Your browser does not support the video tag.
</video>
Correct: valid URL in src
<videocontrols>
<sourcesrc="movie.mp4"type="video/mp4">
Your browser does not support the video tag.
</video>
Incorrect: empty src on <source> in a picture element
<picture>
<sourcesrc=""type="image/webp">
<imgsrc="photo.jpg"alt="A sunset over the ocean">
</picture>
Note that <source> inside <picture> uses srcset, not src. This example is doubly wrong — the attribute is both empty and incorrect. Here is the fix:
Correct: using srcset with a valid URL in a picture element
<picture>
<sourcesrcset="photo.webp"type="image/webp">
<imgsrc="photo.jpg"alt="A sunset over the ocean">
</picture>
Incorrect: multiple sources with one empty src
<audiocontrols>
<sourcesrc="song.ogg"type="audio/ogg">
<sourcesrc=""type="audio/mpeg">
</audio>
Correct: remove the source if no file is available
<audiocontrols>
<sourcesrc="song.ogg"type="audio/ogg">
<sourcesrc="song.mp3"type="audio/mpeg">
</audio>
Correct: conditionally add sources with JavaScript
If the URL is determined at runtime, avoid placing an empty <source> in your markup. Instead, add it dynamically:
<videoid="player"controls>
Your browser does not support the video tag.
</video>
<script>
constvideoUrl=getVideoUrl();// your logic here
if(videoUrl){
constsource=document.createElement("source");
source.src=videoUrl;
source.type="video/mp4";
document.getElementById("player").appendChild(source);
}
</script>
This approach keeps your HTML valid at all times and only inserts a <source> element when a real URL is available.
The srcset attribute on img and source elements accepts a comma-separated list of image candidate strings. Each candidate consists of a URL optionally followed by a width descriptor (e.g., 300w) or a pixel density descriptor (e.g., 2x). The URL in each candidate must conform to the URL Standard, which does not permit raw square brackets in the query string of an HTTP or HTTPS URL.
This issue commonly arises when a backend framework or CMS generates URLs that use square brackets in query parameters — for example, ?filter[size]=large or ?dimensions[]=300. While many browsers are lenient and will load these URLs anyway, they are technically invalid according to the URL specification. Using invalid URLs can lead to unpredictable behavior across different browsers, HTML parsers, and tools that process your markup. It also means your HTML fails W3C validation, which can mask other, more critical issues in your code.
You have two main approaches to fix this:
Percent-encode the brackets. Replace every
[with%5Band every]with%5D. This preserves the intended query parameter structure while making the URL spec-compliant. Your server should interpret percent-encoded brackets identically to raw brackets.Eliminate brackets from the URL. If you control the server-side code, consider using alternative query parameter conventions that don't rely on brackets — for instance, using dot notation (
filter.size=large), comma-separated values (dimensions=300,400), or repeated parameter names (dimension=300&dimension=400).
When fixing these URLs, also make sure each image candidate follows the correct format: a valid URL, followed by optional whitespace and a descriptor, with candidates separated by commas.
Examples
Incorrect — raw square brackets in query string
This triggers the validation error because [ and ] appear unescaped in the srcset URLs:
<img
src="photo.jpg"
srcset="photo.jpg?crop[width]=400 400w, photo.jpg?crop[width]=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Fixed — percent-encoded brackets
Replacing [ with %5B and ] with %5D makes the URLs valid:
<img
src="photo.jpg"
srcset="photo.jpg?crop%5Bwidth%5D=400 400w, photo.jpg?crop%5Bwidth%5D=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Fixed — brackets removed from URL design
If you can modify the server-side routing, restructuring the query parameters avoids the issue entirely:
<img
src="photo.jpg"
srcset="photo.jpg?crop_width=400 400w, photo.jpg?crop_width=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Incorrect — brackets with pixel density descriptors
The same problem occurs regardless of whether you use width descriptors or density descriptors:
<img
src="avatar.jpg"
srcset="avatar.jpg?size=[sm] 1x, avatar.jpg?size=[lg] 2x"
alt="User avatar">
Fixed — percent-encoded version
<img
src="avatar.jpg"
srcset="avatar.jpg?size=%5Bsm%5D 1x, avatar.jpg?size=%5Blg%5D 2x"
alt="User avatar">
Fixed — simplified query parameters
<img
src="avatar.jpg"
srcset="avatar.jpg?size=sm 1x, avatar.jpg?size=lg 2x"
alt="User avatar">
An srcset attribute must not end with a trailing comma, as each entry must be a complete image candidate string with a URL and an optional width or pixel density descriptor.
The srcset attribute accepts a comma-separated list of image candidate strings. Each string contains a URL and, optionally, a width descriptor (like 400w) or a pixel density descriptor (like 2x). A trailing comma after the last entry creates an empty image candidate string, which is invalid.
This error often appears when srcset values are built dynamically by a template engine or CMS that appends a comma after every item, including the last one.
Invalid example
<img
srcset="image-small.jpg 400w, image-medium.jpg 800w, image-large.jpg 1200w,"
image-medium.jpg 800w,
image-large.jpg 1200w,
sizes="(max-width: 800px) 400px, 800px"
src="image-medium.jpg"
alt="A sample photo"
>
The trailing comma after image-large.jpg 1200w, produces the validation error.
Fixed example
<img
srcset="image-small.jpg 400w, image-medium.jpg 800w, image-large.jpg 1200w"
image-medium.jpg 800w,
image-large.jpg 1200w
sizes="(max-width: 800px) 400px, 800px"
src="image-medium.jpg"
alt="A sample photo"
>
Removing the trailing comma after the last image candidate string fixes the issue.
The srcset attribute allows you to provide multiple image sources so the browser can choose the most appropriate one based on the user's device and viewport. There are two distinct modes for srcset:
- Width descriptor mode — Each candidate specifies the image's intrinsic width using a
wdescriptor (e.g.,640w). This mode requires thesizesattribute so the browser knows how much space the image will occupy in the layout, enabling it to pick the best candidate. - Density descriptor mode — Each candidate specifies a pixel density using an
xdescriptor (e.g.,2x). This mode does not use thesizesattribute; the browser simply matches candidates to the device's pixel density.
These two modes are mutually exclusive. You cannot mix w and x descriptors in the same srcset, and you cannot use x descriptors (or bare URLs with no descriptor) when sizes is present. The HTML specification is explicit about this: if sizes is specified, all image candidate strings must include a width descriptor.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard defines strict parsing rules for
srcset. Whensizesis present, the browser's source selection algorithm expects width descriptors. Providing density descriptors or bare candidates in this context violates the spec and produces undefined behavior. - Broken image selection: Browsers rely on the
sizesattribute to calculate whichw-described image best fits the current layout width. If you providexdescriptors alongsidesizes, the browser may ignore thesrcsetentirely or fall back to thesrcattribute, defeating the purpose of responsive images. - Accessibility and performance: Responsive images exist to serve appropriately sized files to different devices. An invalid
srcset/sizescombination can result in oversized images being downloaded on small screens (wasting bandwidth) or undersized images on large screens (reducing visual quality).
How to fix it
You have two options:
- Keep
sizesand switch to width descriptors — Replace everyxdescriptor (or missing descriptor) insrcsetwith the actual intrinsic pixel width of each image file, expressed with awsuffix. - Remove
sizesand keep density descriptors — If you only need to serve different resolutions for high-DPI screens at a fixed layout size, drop thesizesattribute and usexdescriptors.
When using width descriptors, the value must match the image file's actual intrinsic width in pixels. For example, if photo-640.jpg is 640 pixels wide, its descriptor should be 640w.
Examples
Invalid: sizes present with density descriptors
This triggers the error because 1x and 2x are density descriptors, but sizes requires width descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Invalid: sizes present with a bare candidate (no descriptor)
A candidate with no descriptor defaults to 1x, which is a density descriptor — still invalid when sizes is present.
<img
src="photo-640.jpg"
srcset="photo-640.jpg, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Fix: use width descriptors with sizes
Each candidate now specifies the intrinsic width of the image file. The browser uses the sizes value to determine which image to download.
<img
src="photo-640.jpg"
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Alternative fix: remove sizes and use density descriptors
If you don't need the browser to choose images based on layout width — for example, the image always renders at a fixed CSS size — drop sizes and use x descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
alt="A mountain landscape">
Using width descriptors with source inside picture
The same rule applies to source elements inside a picture. If sizes is present, every candidate must use a w descriptor.
<picture>
<source
srcset="hero-480.webp 480w, hero-960.webp 960w, hero-1920.webp 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
type="image/webp">
<img
src="hero-960.jpg"
srcset="hero-480.jpg 480w, hero-960.jpg 960w, hero-1920.jpg 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="A hero banner image">
</picture>
When an img element has a sizes attribute, every image candidate in the srcset attribute must include a width descriptor (like 480w), not a pixel density descriptor (like 2x) or a bare URL.
The srcset attribute accepts two types of descriptors, but they cannot be mixed, and the choice depends on whether sizes is present.
Width descriptors (w) tell the browser the actual pixel width of each source image. The browser then uses the sizes attribute to determine how much space the image will occupy in the layout and picks the best candidate from srcset. This pairing of srcset with width descriptors and sizes is how responsive image selection works.
Pixel density descriptors (x) tell the browser which image to use based on the device's pixel density (e.g., 1x for standard screens, 2x for retina). When using density descriptors, the sizes attribute has no role and should be omitted.
The validation error appears when sizes is present but one or more entries in srcset lack a w descriptor. A bare URL with no descriptor is treated as 1x by default, which conflicts with the requirement that sizes demands width descriptors.
Invalid example
<img
src="photo-800.jpg"
srcset="photo-400.jpg, photo-800.jpg 2x"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
Here, photo-400.jpg has no descriptor (defaults to 1x), and photo-800.jpg uses 2x. Because sizes is present, the validator expects every candidate to specify a width.
Fixed example
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
Each candidate now includes a width descriptor that reflects the image's intrinsic width in pixels. The browser compares these widths against the resolved sizes value to choose the most appropriate source.
If you do not need sizes and just want to serve different images for different pixel densities, drop the sizes attribute and use density descriptors instead:
<img
src="photo-400.jpg"
srcset="photo-400.jpg 1x, photo-800.jpg 2x"
alt="A landscape photo">
The srcset attribute supports two types of descriptors: width descriptors (like 480w) and pixel density descriptors (like 2x). However, these two types cannot be mixed, and the sizes attribute is only compatible with width descriptors. The sizes attribute tells the browser how wide the image will be displayed at various viewport sizes, and the browser uses this information along with the width descriptors in srcset to choose the most appropriate image file. If sizes is present but an image candidate lacks a width descriptor, the browser cannot perform this calculation correctly.
This matters for several reasons. First, it violates the WHATWG HTML specification, which explicitly requires that when sizes is present, all image candidates must use width descriptors. Second, browsers may ignore malformed srcset values or fall back to unexpected behavior, resulting in the wrong image being loaded — potentially hurting performance by downloading unnecessarily large files or degrading visual quality by selecting a too-small image. Third, standards-compliant markup ensures consistent, predictable behavior across all browsers and devices.
A common mistake is specifying a plain URL without any descriptor, or mixing density descriptors (1x, 2x) with the sizes attribute. An image candidate string without any descriptor defaults to 1x, which is a density descriptor — and that conflicts with the presence of sizes.
Examples
❌ Incorrect: Missing width descriptor with sizes present
<picture>
<source
srcset="image-small.jpg, image-large.jpg 1024w"
sizes="(max-width: 600px) 480px, 800px">
<imgsrc="image-fallback.jpg"alt="A scenic landscape">
</picture>
Here, image-small.jpg has no width descriptor. Since sizes is present, this triggers the validation error.
❌ Incorrect: Using density descriptors with sizes
<img
srcset="image-1x.jpg 1x, image-2x.jpg 2x"
sizes="(max-width: 600px) 480px, 800px"
src="image-fallback.jpg"
alt="A scenic landscape">
Density descriptors (1x, 2x) are incompatible with the sizes attribute.
✅ Correct: All candidates have width descriptors
<picture>
<source
srcset="image-small.jpg 480w, image-large.jpg 1024w"
sizes="(max-width: 600px) 480px, 800px">
<imgsrc="image-fallback.jpg"alt="A scenic landscape">
</picture>
Every image candidate now includes a width descriptor, which pairs correctly with the sizes attribute.
✅ Correct: Using density descriptors without sizes
If you want to use density descriptors instead of width descriptors, simply remove the sizes attribute:
<img
srcset="image-1x.jpg 1x, image-2x.jpg 2x"
src="image-fallback.jpg"
alt="A scenic landscape">
This is valid because density descriptors don't require (and shouldn't be used with) the sizes attribute.
✅ Correct: Width descriptors on <img> with sizes
<img
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w"
sizes="(max-width: 400px) 320px, (max-width: 800px) 640px, 1280px"
src="photo-640.jpg"
alt="A close-up of a flower">
Each entry in srcset specifies its intrinsic width, and sizes tells the browser which display width to expect at each breakpoint. The browser then selects the best-fitting image automatically.
The tabindex attribute controls whether and in what order an element can receive keyboard focus. The W3C validator reports this error when it encounters tabindex="" because the HTML specification requires the attribute's value to be a valid integer — and an empty string cannot be parsed as one. This commonly happens when a value is accidentally omitted, when a template engine outputs a blank value, or when a CMS generates the attribute without a proper default.
Why this matters
Keyboard navigation is fundamental to web accessibility. Screen readers and assistive technologies rely on tabindex values to determine focus behavior. An empty tabindex attribute creates ambiguity: browsers may ignore it or handle it inconsistently, leading to unpredictable focus behavior for keyboard and assistive technology users. Beyond accessibility, it also means your HTML is invalid according to the WHATWG HTML living standard, which strictly defines tabindex as accepting only a valid integer.
How tabindex works
The attribute accepts an integer value with three meaningful ranges:
- Negative value (e.g.,
tabindex="-1"): The element can be focused programmatically (via JavaScript's.focus()), but it is excluded from the sequential keyboard tab order. tabindex="0": The element is added to the natural tab order based on its position in the document source. This is the most common way to make non-interactive elements (like a<div>or<span>) keyboard-focusable.- Positive value (e.g.,
tabindex="1",tabindex="5"): The element is placed in the tab order ahead of elements withtabindex="0"or notabindex, with lower numbers receiving focus first. Using positive values is generally discouraged because it overrides the natural document order and makes focus management harder to maintain.
How to fix it
- If the element should be focusable in tab order, set
tabindex="0". - If the element should only be focusable programmatically, set
tabindex="-1". - If you don't need custom focus behavior, remove the
tabindexattribute entirely. Interactive elements like<a>,<button>, and<input>are already focusable by default. - If a template or CMS generates the attribute, ensure the logic provides a valid integer or omits the attribute when no value is available.
Examples
❌ Empty tabindex value (triggers the error)
<divtabindex="">Click me</div>
<buttontabindex="">Submit</button>
✅ Fixed: valid integer provided
<divtabindex="0">Click me</div>
<buttontabindex="-1">Submit</button>
✅ Fixed: attribute removed when not needed
Interactive elements like <button> are focusable by default, so tabindex can simply be removed:
<button>Submit</button>
✅ Fixed: conditional rendering in a template
If you're using a templating system, ensure the attribute is only rendered when a valid value exists:
<!-- Instead of always outputting tabindex -->
<!-- Bad: <div tabindex="{{ tabindexValue }}"> -->
<!-- Only output the attribute when a value is set -->
<divtabindex="0">Interactive content</div>
A note on positive tabindex values
While positive integers like tabindex="1" or tabindex="3" are technically valid, they force a custom tab order that diverges from the visual and DOM order of the page. This creates confusion for keyboard users and is difficult to maintain as pages evolve. The WAI-ARIA Authoring Practices recommend avoiding positive tabindex values. Stick with 0 and -1 in nearly all cases.
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