HTML Guides for img
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.
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() or encodeURIComponent().
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">
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 %0A or %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 src value 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() or strip() 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 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 srcset entry has no descriptor at all — the URL is listed without any accompanying width or density value.
- A pixel density descriptor (x) is used alongside sizes — mixing sizes with x descriptors is invalid because the two mechanisms are mutually exclusive.
- A typo or formatting issue — for example, writing 600px instead of 600w, or placing a comma incorrectly.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly states that when sizes is 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 srcset defeats 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 x descriptors when sizes is present. If you need density descriptors, remove the sizes attribute entirely.
- Make sure every entry has a descriptor — bare URLs without any descriptor are invalid when sizes is 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 srcset attribute is empty (srcset="")
- The srcset attribute 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 src attribute 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 w descriptor (e.g., 400w). This mode requires the sizes attribute 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 x descriptor (e.g., 2x). This mode must not include a sizes attribute.
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.
According to the HTML specification, the width and height attributes on img elements accept only valid non-negative integers. A valid non-negative integer consists of one or more ASCII digits (0–9) with no other characters — no decimal points, no spaces, no units like px. When the validator encounters a value such as 602.88, it parses the digits 602 successfully, then hits the . character where it expects another digit or the end of the value, triggering the error.
This issue commonly arises when dimension values are generated programmatically — for example, when a CMS, image processing tool, or JavaScript calculation produces floating-point numbers and outputs them directly into the HTML. It can also happen when copying dimension values from CSS or design tools that work in sub-pixel units.
Why this matters
- Standards compliance: The HTML specification is explicit that these attributes take integer values. Using decimals produces invalid markup.
- Unpredictable rendering: Browsers may handle the malformed value in different ways — some might truncate at the decimal point, others might ignore the attribute entirely. This can lead to layout shifts or incorrectly sized images.
- Layout stability: The width and height attributes are used by browsers to calculate the aspect ratio of an image before it loads, which helps prevent Cumulative Layout Shift (CLS). Invalid values can undermine this behavior, causing content to jump around as images load.
How to fix it
- Round to the nearest integer. If your value is 602.88, round it to 603. If it’s 800.2, round to 800.
- Remove the decimal point entirely. The value must contain only digits.
- Do not include units. Values like 800px are also invalid; use just 800.
- Fix the source of the values. If your CMS or build tool generates these attributes, update the logic to output integers (e.g., using Math.round() in JavaScript or round() in PHP/Python).
Examples
❌ Incorrect: decimal values in width and height
<img src="photo.jpg" alt="A golden retriever" height="602.88" width="800.2">
The validator reports errors for both attributes because . is not a valid character in a non-negative integer.
✅ Correct: whole number values
<img src="photo.jpg" alt="A golden retriever" height="603" width="800">
Both values are valid non-negative integers with no decimal points.
❌ Incorrect: trailing decimal point with no fractional part
<img src="banner.png" alt="Sale banner" width="1200." height="400.">
Even a trailing . with nothing after it is invalid — the parser still encounters an unexpected character.
✅ Correct: clean integer values
<img src="banner.png" alt="Sale banner" width="1200" height="400">
Using CSS for sub-pixel precision
If you genuinely need sub-pixel sizing (which is rare for images), use CSS instead of HTML attributes. CSS width and height properties do accept decimal values:
<img src="icon.svg" alt="Settings icon" style="width: 24.5px; height: 24.5px;">
However, keep in mind that you should still provide integer width and height HTML attributes for aspect ratio hinting, and then override with CSS if sub-pixel precision is needed:
<img
src="icon.svg"
alt="Settings icon"
width="25"
height="25"
style="width: 24.5px; height: 24.5px;">
This approach gives you valid HTML, proper aspect ratio hints for layout stability, and the precise sizing you need.
The HTML specification defines the width attribute on <img> elements as a “valid non-negative integer” — essentially a string of digits with no units, no decimals, and no percentage signs. When you write something like width="100%", the validator expects a digit character but encounters %, producing this error. While some older browsers historically accepted percentage values in the width attribute (a holdover from pre-HTML5 conventions), this was never part of the modern HTML standard and should not be relied upon.
This matters for several reasons. First, standards compliance ensures your markup behaves consistently across browsers and devices. Second, assistive technologies and browser layout engines may interpret an invalid width value unpredictably — some may ignore it entirely, others may parse it incorrectly. Third, the width and height attributes on <img> serve an important role in reserving layout space before the image loads (preventing Cumulative Layout Shift), but they only work correctly when set to valid integer pixel values that reflect the image’s intrinsic or intended pixel dimensions.
How to fix it
If you want a fixed pixel width, provide just the integer without any unit:
<img src="photo.jpg" alt="A red car" width="600">
If you need a percentage-based or responsive width, remove the width attribute from the HTML and use CSS instead. You can apply the style inline, via a <style> block, or in an external stylesheet.
If you want to preserve aspect ratio and prevent layout shift, keep the width and height attributes set to values that represent the image’s intrinsic aspect ratio (in pixels), and then override the displayed size with CSS.
Examples
❌ Invalid: percentage in the width attribute
<img src="photo.jpg" alt="A red car" width="100%">
This triggers the error because 100% is not a valid non-negative integer.
❌ Invalid: other non-integer values
<img src="photo.jpg" alt="A red car" width="50%">
<img src="banner.jpg" alt="Sale banner" width="300px">
<img src="icon.png" alt="Settings icon" width="2.5">
Units like px, percentage signs, and decimal points are all invalid in the width attribute.
✅ Fixed: using a pixel integer
<img src="photo.jpg" alt="A red car" width="800" height="600">
✅ Fixed: percentage width via inline CSS
<img src="photo.jpg" alt="A red car" style="width: 100%;">
✅ Fixed: responsive image with preserved aspect ratio
This approach sets the intrinsic dimensions in the attributes (to reserve layout space) and uses CSS to make the image responsive:
<style>
.responsive-img {
width: 100%;
height: auto;
}
</style>
<img src="photo.jpg" alt="A red car" width="800" height="600" class="responsive-img">
The browser uses the width and height attribute values to calculate the aspect ratio and reserve the correct amount of space, while CSS controls the actual rendered size. This is the recommended approach for responsive images because it avoids layout shift while still allowing flexible sizing.
In HTML, the width and height attributes on elements like <img> and <iframe> are defined as accepting only valid non-negative integers. According to the HTML specification, the value is implicitly in CSS pixels, so appending px or any other unit is both unnecessary and invalid. The parser expects every character in the value to be a digit (0–9), and when it encounters a letter like p, it reports the error.
This is a common mistake, especially for developers who frequently work with CSS, where px units are required. In HTML attributes, however, the convention is different — the pixel unit is implied, and adding it creates a malformed value. Browsers may still attempt to parse the number by ignoring the trailing characters, but this behavior is not guaranteed and should not be relied upon.
Getting these attributes right matters for several reasons:
- Standards compliance ensures your markup is predictable and portable across all browsers and user agents.
- Layout stability depends on the browser correctly reading width and height to reserve space for images and iframes before they load, preventing cumulative layout shift (CLS). A malformed value could cause the browser to fall back to default sizing or ignore the attribute entirely.
- Accessibility tools and screen readers may use these attributes to convey information about embedded content, and invalid values could interfere with that process.
If you need to set dimensions using units other than pixels (such as percentages or viewport units), use CSS instead of HTML attributes.
Examples
❌ Invalid: using px in the attribute value
<img src="cat.jpg" alt="A cat sitting on a windowsill" width="225px" height="100px">
The validator reports an error because 225px and 100px contain the non-digit characters px.
✅ Valid: plain integers without units
<img src="cat.jpg" alt="A cat sitting on a windowsill" width="225" height="100">
❌ Invalid: using percentage in the attribute value
<iframe src="embed.html" width="100%" height="400px" title="Embedded content"></iframe>
Both 100% and 400px are invalid because they contain non-digit characters.
✅ Valid: plain integers on an <iframe>
<iframe src="embed.html" width="800" height="400" title="Embedded content"></iframe>
✅ Using CSS when you need non-pixel units
If you need percentage-based or responsive sizing, apply it through CSS rather than HTML attributes:
<iframe src="embed.html" style="width: 100%; height: 400px;" title="Embedded content"></iframe>
Or better yet, use an external stylesheet:
<style>
.responsive-frame {
width: 100%;
height: 400px;
}
</style>
<iframe src="embed.html" class="responsive-frame" title="Embedded content"></iframe>
Quick reference of invalid vs. valid values
| Invalid value | Problem | Valid alternative |
|---|---|---|
| 225px | Contains px | 225 |
| 100% | Contains % | Use CSS instead |
| 20em | Contains em | Use CSS instead |
| auto | Not a number | Use CSS instead |
| 10.5 | Decimal point | 10 or 11 |
The fix is straightforward: strip any unit suffixes from width and height HTML attributes and provide plain integer values. For anything beyond simple pixel dimensions, move your sizing logic to CSS.
The src attribute is one of two required attributes on every <img> element (the other being alt). The HTML specification mandates src because an image element without a source has no meaningful content to render. Omitting it produces invalid markup and leads to unpredictable browser behavior — some browsers may display a broken image icon, while others may render nothing at all.
This issue commonly occurs in a few scenarios:
- Templating placeholders — A developer adds an <img> tag intending to populate the src dynamically but forgets to set a default value.
- Lazy loading implementations — Some lazy-loading scripts store the real URL in a data-src attribute and omit src entirely, which results in invalid HTML.
- Incomplete markup — The attribute is simply forgotten during development.
Beyond validation, a missing src attribute causes accessibility problems. Screen readers rely on well-formed <img> elements to convey image information to users. A malformed image element can confuse assistive technologies and degrade the user experience.
How to fix it
- Add a src attribute with a valid URL pointing to your image.
- Always include an alt attribute as well — it’s also required and provides a text alternative for the image.
- If you’re using lazy loading and want to defer the actual image source, set src to a lightweight placeholder (such as a tiny transparent image or a low-quality preview) and use the native loading="lazy" attribute instead of removing src.
Examples
❌ Missing src attribute
<img alt="A sunset over the ocean">
This triggers the validation error because src is absent.
❌ Source stored only in a data- attribute
<img data-src="/images/photo.jpg" alt="A sunset over the ocean">
While data-src is a valid custom data attribute, it does not satisfy the requirement for src. The validator will still report the error.
✅ Correct usage with src and alt
<img src="/images/photo.jpg" alt="A sunset over the ocean">
✅ Lazy loading with a placeholder src
<img
src="/images/photo-placeholder.jpg"
data-src="/images/photo-full.jpg"
alt="A sunset over the ocean"
loading="lazy">
Here, src points to a small placeholder image so the markup stays valid, while data-src holds the full-resolution URL for your lazy-loading script to swap in.
✅ Native lazy loading (no JavaScript needed)
<img src="/images/photo.jpg" alt="A sunset over the ocean" loading="lazy">
Modern browsers support the loading="lazy" attribute natively, so you can keep a valid src and still defer off-screen images without any custom scripting.
The <picture> element is a container that lets you provide multiple image sources for different viewport sizes, resolutions, or format support. It works together with zero or more <source> elements and exactly one <img> element. The browser evaluates each <source> in order, selects the best match based on its media, type, or srcset attributes, and then displays the chosen image in the space occupied by the <img> element. If no <source> matches—or if the browser doesn’t support the <picture> element at all—the <img> element’s src attribute is used as the final fallback.
The <img> element isn’t optional; it’s structurally required by the HTML specification. Without it, the <picture> element is incomplete and invalid. This matters for several reasons:
- Rendering: Browsers rely on the <img> element to actually display the image. A <picture> element with only <source> children will render nothing in most browsers.
- Accessibility: The <img> element carries the alt attribute, which provides a text alternative for screen readers and other assistive technologies. Without it, there’s no accessible description of the image.
- Fallback support: Older browsers that don’t understand <picture> or <source> will ignore those elements entirely and fall back to the <img>. Without it, those users see nothing.
- Standards compliance: The WHATWG HTML specification explicitly states that the <picture> element’s content model requires one <img> element, optionally preceded by <source> elements and inter-element whitespace.
The <img> element should always be the last child inside <picture>, placed after all <source> elements.
Examples
Invalid: <picture> without an <img> element
This markup triggers the validation error because the required <img> child is missing:
<picture>
<source srcset="hero-large.webp" type="image/webp">
<source srcset="hero-large.jpg" type="image/jpeg">
</picture>
Fixed: Adding the required <img> element
Add an <img> as the last child with a src pointing to the default image and a descriptive alt attribute:
<picture>
<source srcset="hero-large.webp" type="image/webp">
<source srcset="hero-large.jpg" type="image/jpeg">
<img src="hero-large.jpg" alt="A panoramic view of the mountain landscape">
</picture>
Using <picture> for responsive images with media queries
When serving different image sizes based on viewport width, the <img> element provides the default (typically the smallest) image:
<picture>
<source srcset="banner-wide.jpg" media="(min-width: 1200px)">
<source srcset="banner-medium.jpg" media="(min-width: 600px)">
<img src="banner-small.jpg" alt="Promotional banner for summer sale">
</picture>
The browser checks each <source> in order. If the viewport is 1200 pixels or wider, banner-wide.jpg is used. If it’s between 600 and 1199 pixels, banner-medium.jpg is used. Otherwise, the <img> element’s src value of banner-small.jpg is displayed.
Using <picture> for format selection
You can also use <picture> to offer modern image formats with a fallback for browsers that don’t support them:
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Close-up of a sunflower in bloom">
</picture>
Browsers that support AVIF will use the first source, those that support WebP will use the second, and all others will fall back to the JPEG specified in the <img> element. In every case, the <img> element is what makes the <picture> element valid and functional.
When the HTML parser encounters an <svg> or <math> tag, it switches from the HTML namespace into a foreign namespace (SVG or MathML, respectively). Inside these foreign contexts, only elements defined by those specifications are valid. The HTML <img> element does not exist in the SVG or MathML namespaces, so placing it there causes a parsing conflict and triggers the W3C validator error: “HTML start tag img in a foreign namespace context.”
This matters for several reasons. Browsers handle this situation inconsistently — some may break out of the foreign namespace to render the <img>, while others may ignore it entirely or produce unexpected layout results. This inconsistency means your page may look correct in one browser but be broken in another. Beyond rendering issues, assistive technologies rely on proper namespace boundaries to interpret content. An <img> in the wrong namespace may lose its accessibility semantics, meaning the alt text could go unannounced to screen reader users.
How to fix it
The fix depends on which foreign namespace context contains the <img>:
Inside <svg>: Replace <img> with the SVG <image> element. Note that SVG’s <image> uses the href attribute (not src) and requires x, y, width, and height attributes for positioning and sizing. Unfortunately, SVG’s <image> element does not support an alt attribute directly — use a <title> child element or aria-label to provide an accessible name.
Inside <math>: Move the <img> outside the <math> element entirely, or place it inside an <mtext> element. The <mtext> element in MathML is designed to hold text content and, per the HTML parsing rules, can contain embedded HTML elements.
Examples
Incorrect: <img> inside <svg>
<svg width="200" height="200">
<img src="diagram.png" alt="A diagram" width="200" height="200">
</svg>
Correct: using SVG <image> element
<svg width="200" height="200" role="img" aria-label="A diagram">
<image href="diagram.png" x="0" y="0" width="200" height="200" />
</svg>
Correct: with an accessible name via <title>
<svg width="200" height="200" role="img" aria-labelledby="diagram-title">
<title id="diagram-title">A diagram</title>
<image href="diagram.png" x="0" y="0" width="200" height="200" />
</svg>
Incorrect: <img> inside <math>
<math>
<img src="equation.png" alt="x squared plus one">
</math>
Correct: <img> inside <mtext> within <math>
<math>
<mtext>
<img src="equation.png" alt="x squared plus one">
</mtext>
</math>
Correct: <img> moved outside <math>
<img src="equation.png" alt="x squared plus one">
Incorrect: mixed HTML content inside <svg>
<svg width="300" height="150">
<rect width="300" height="150" fill="#eee" />
<img src="icon.png" alt="Icon" width="32" height="32">
<text x="50" y="80">Hello</text>
</svg>
Correct: using <image> alongside other SVG elements
<svg width="300" height="150">
<rect width="300" height="150" fill="#eee" />
<image href="icon.png" x="10" y="10" width="32" height="32" aria-label="Icon" />
<text x="50" y="80">Hello</text>
</svg>
If you need to place HTML content alongside or on top of an SVG, consider using the <foreignObject> SVG element, which explicitly creates an HTML namespace context within SVG:
<svg width="300" height="150">
<rect width="300" height="150" fill="#eee" />
<foreignObject x="10" y="10" width="100" height="100">
<img src="icon.png" alt="Icon" width="32" height="32">
</foreignObject>
</svg>
The <foreignObject> approach is especially useful when you need full HTML capabilities — such as alt text on <img>, form controls, or rich text — inside an SVG graphic.
In earlier versions of HTML, the border attribute on <img> was commonly used to control the border width in pixels. Its most frequent use was border="0" to suppress the default blue border browsers would render around images wrapped in <a> links. While this worked, it mixed presentation with markup — something HTML5 discourages in favor of a clean separation between structure (HTML) and styling (CSS).
The W3C HTML Validator flags this attribute as obsolete because it was removed from the HTML5 specification. Modern browsers still understand it for backward compatibility, but relying on deprecated features is bad practice. It can lead to inconsistencies across browsers, makes your code harder to maintain, and signals to validators and other developers that the markup is outdated.
The recommended approach is to handle image borders entirely in CSS. If you previously used border="0" to remove borders from linked images, most modern CSS resets and normalize stylesheets already handle this. If you’re not using a reset, a single CSS rule takes care of it globally — no need to repeat the attribute on every <img> tag.
How to fix it
- Remove the border attribute from all <img> elements.
- Add a CSS rule to achieve the same effect. For removing borders, use img { border: 0; } in your stylesheet. For adding a visible border, use properties like border: 2px solid #333;.
Examples
❌ Obsolete border attribute
<a href="/products">
<img src="product.jpg" alt="Our product" border="0">
</a>
This triggers the validator warning because border is no longer a valid attribute on <img>.
✅ Fixed with CSS (external/internal stylesheet)
<style>
img {
border: 0;
}
</style>
<a href="/products">
<img src="product.jpg" alt="Our product">
</a>
A single rule in your stylesheet removes borders from all images, which is cleaner and easier to maintain than repeating the attribute on every element.
✅ Fixed with inline CSS (if needed)
<a href="/products">
<img src="product.jpg" alt="Our product" style="border: 0;">
</a>
Inline styles work but aren’t ideal for large-scale fixes. Prefer a stylesheet rule when possible.
✅ Adding a decorative border with CSS
If your intent was to add a visible border (e.g., border="2"), replace it with a more flexible CSS equivalent:
<style>
.bordered {
border: 2px solid #333;
}
</style>
<img src="photo.jpg" alt="A scenic landscape" class="bordered">
CSS gives you far more control — you can specify the border style, color, individual sides, and even use border-radius for rounded corners, none of which were possible with the old border attribute.
Every HTML element has an implicit ARIA role defined by the HTML specification. The <img> element’s implicit role is img, which means assistive technologies like screen readers already recognize it as an image without any additional ARIA attributes. Adding role="img" explicitly doesn’t change behavior — it just adds unnecessary noise to your markup and signals that the author may not understand how native semantics work.
The W3C validator flags this because it violates the first rule of ARIA: don’t use ARIA if you can use a native HTML element or attribute that already has the semantics you need. Redundant roles clutter your code, make maintenance harder, and can confuse other developers into thinking the role is there for a specific reason.
The role="img" attribute is genuinely useful in other contexts — for example, when you want to group multiple elements together and have them treated as a single image by assistive technologies. A <div> or <span> has no implicit img role, so adding role="img" to a container is meaningful and appropriate.
How to fix it
Simply remove the role="img" attribute from any <img> element. The image semantics are already built in. Make sure you still provide a meaningful alt attribute for accessibility.
Examples
❌ Redundant role on <img>
<img src="photo.jpg" alt="A sunset over the ocean" role="img">
The validator will warn: The “img” role is unnecessary for element “img”.
✅ Fixed: Remove the redundant role
<img src="photo.jpg" alt="A sunset over the ocean">
No explicit role is needed. The browser already communicates this element as an image.
✅ Legitimate use of role="img" on a non-image element
The role="img" attribute is appropriate when applied to a container that groups multiple elements into a single conceptual image:
<div role="img" aria-label="Star rating: 4 out of 5">
<span>⭐</span>
<span>⭐</span>
<span>⭐</span>
<span>⭐</span>
<span>☆</span>
</div>
Here, the <div> has no inherent image semantics, so role="img" is meaningful — it tells assistive technologies to treat the entire group as a single image described by the aria-label.
✅ Another legitimate use: CSS background image with role="img"
<div role="img" aria-label="Company logo" class="logo-background"></div>
Since a <div> styled with a CSS background image has no image semantics, role="img" paired with aria-label ensures the visual content is accessible.
The longdesc attribute dates back to HTML4, where it accepted a URL pointing to a separate page (or section of a page) containing a detailed description of the image. The idea was to supplement the short text in the alt attribute with a more comprehensive explanation, particularly useful for complex images like charts, diagrams, or infographics.
HTML5 made longdesc obsolete for several reasons. Browser support was inconsistent — most browsers never exposed the attribute in a way that was easily discoverable by users. Many developers misused it by placing literal descriptions in the attribute instead of URLs, or left it pointing to broken links. Because the attribute was invisible in the rendered page, there was no visual indication that a longer description existed, making it practically useless for sighted users and unreliable for assistive technology users.
The recommended replacements are more robust and accessible:
- Wrap the image in an a element (or place a link nearby) that points to the description page. This makes the link visible and usable by everyone.
- Use aria-describedby to reference a description that already exists on the same page. This is ideal when the detailed description is displayed alongside the image.
- Use a figure with figcaption to associate a visible caption or description directly with the image.
These approaches are better for accessibility because they work reliably across browsers and assistive technologies, and they make the description discoverable to all users, not just those using specific screen readers that happened to support longdesc.
Examples
❌ Obsolete: using longdesc
<img
src="cat.jpg"
alt="Smiling cat sitting on a windowsill"
longdesc="descriptions/smiling-cat.html">
This triggers the validation error because longdesc is no longer a valid attribute on img in HTML5.
✅ Fix: wrap the image in a link
The simplest replacement is to make the image itself a link to the description:
<a href="descriptions/smiling-cat.html">
<img src="cat.jpg" alt="Smiling cat sitting on a windowsill">
</a>
✅ Fix: provide a separate link near the image
If you don’t want the image itself to be clickable, place a visible link nearby:
<figure>
<img src="chart.png" alt="Quarterly revenue chart for 2024">
<figcaption>
Quarterly revenue chart.
<a href="descriptions/revenue-chart.html">View detailed description</a>
</figcaption>
</figure>
✅ Fix: use aria-describedby for on-page descriptions
When the long description is already on the same page, reference it with aria-describedby:
<figure>
<img
src="chart.png"
alt="Quarterly revenue chart for 2024"
aria-describedby="chart-description">
<figcaption id="chart-description">
Revenue grew from $2.1M in Q1 to $3.8M in Q4, with the largest
quarter-over-quarter increase occurring between Q2 and Q3.
</figcaption>
</figure>
This approach keeps the description visible on the page and programmatically associates it with the image for screen readers.
Choosing the right approach
| Scenario | Recommended approach |
|---|---|
| Description is on a separate page | Wrap image in an a element or add a nearby link |
| Description is visible on the same page | Use aria-describedby pointing to the description’s id |
| Image needs a brief visible caption | Use figure with figcaption |
| Complex image (chart, diagram, infographic) | Combine figure, figcaption, and a link to a full description |
In all cases, make sure the alt attribute still provides a meaningful short description. The long description supplements alt — it doesn’t replace it.
The name attribute was historically used on img elements to reference images through JavaScript’s document.images collection or via document.getElementsByName(). In early HTML, name served as an identifier for various elements before the id attribute was widely adopted. The HTML living standard (WHATWG) now marks name as obsolete on img elements, meaning it should no longer be used in new content.
This matters for several reasons:
- Standards compliance: Using obsolete attributes means your HTML does not conform to the current specification, which can cause validation errors and may lead to unexpected behavior in future browser versions.
- Consistency: The id attribute is the universal mechanism for uniquely identifying any HTML element. Using id instead of name keeps your markup consistent and predictable.
- JavaScript and CSS targeting: Modern APIs like document.getElementById() and document.querySelector() work with id, not name on image elements. CSS selectors also target elements by id (e.g., #myImage), making id the more versatile choice.
- Fragment linking: The id attribute allows you to link directly to an element using a URL fragment (e.g., page.html#myImage), whereas the obsolete name attribute on img does not serve this purpose.
To fix this issue, simply replace name with id on your img elements. Keep in mind that id values must be unique within the entire document — no two elements can share the same id. If you have JavaScript code that references the image by name (e.g., document.images["myImage"] or document.getElementsByName("myImage")), update those references to use document.getElementById("myImage") or document.querySelector("#myImage") instead.
Examples
Incorrect: using the obsolete name attribute
<img src="photo.jpg" name="heroImage" alt="A sunset over the ocean">
This triggers the validation error because name is no longer a valid attribute on img.
Correct: using the id attribute
<img src="photo.jpg" id="heroImage" alt="A sunset over the ocean">
The name attribute is replaced with id, and the element can now be targeted with document.getElementById("heroImage") or the CSS selector #heroImage.
Updating JavaScript references
If your existing code references the image by name, update it accordingly.
Before (relying on name):
<img src="logo.png" name="siteLogo" alt="Company logo">
<script>
var logo = document.images["siteLogo"];
logo.style.border = "2px solid blue";
</script>
After (using id):
<img src="logo.png" id="siteLogo" alt="Company logo">
<script>
var logo = document.getElementById("siteLogo");
logo.style.border = "2px solid blue";
</script>
Multiple images that previously shared a name
Since id values must be unique, you cannot give the same id to multiple elements. If you previously used the same name on several images and selected them as a group, switch to a shared class instead:
<img src="slide1.jpg" class="gallery-image" alt="Mountain landscape">
<img src="slide2.jpg" class="gallery-image" alt="Forest trail">
<img src="slide3.jpg" class="gallery-image" alt="River valley">
<script>
var images = document.querySelectorAll(".gallery-image");
images.forEach(function(img) {
img.style.borderRadius = "8px";
});
</script>
This approach is standards-compliant and gives you flexible, modern element selection using class for groups and id for unique elements.
Ready to validate your sites?
Start your free trial today.