HTML Guides for src
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.
When the W3C HTML Validator reports "Expected a slash," it means the URL parser encountered an unexpected character where a / should appear. URLs follow a strict syntax defined by the URL Living Standard. For scheme-based URLs, the format requires the scheme (like https:) to be immediately followed by // and then the authority (hostname). Any deviation — such as a space, a missing slash, or an encoded character in the wrong place — will make the URL invalid.
This matters for several reasons. Browsers may attempt to correct malformed URLs, but their behavior is inconsistent and unpredictable. A broken src attribute can cause images not to load, scripts to fail silently, or media elements to show fallback content. Screen readers and assistive technologies rely on valid URLs to provide meaningful information to users. Search engine crawlers may also fail to follow or index resources with malformed URLs.
Common causes of this error include:
- Accidental spaces inserted within the URL, especially between the scheme and the double slashes (e.g.,
https: //instead ofhttps://). - Missing slashes in the scheme (e.g.,
https:/example.comwith only one slash). - Copy-paste artifacts where invisible characters or line breaks get embedded in the URL string.
- Template or CMS issues where dynamic URL generation introduces unexpected characters.
To fix the issue, carefully inspect the src attribute value and ensure it forms a valid, complete URL with no stray characters. If the URL is generated dynamically, check the code that constructs it.
Examples
Incorrect: space between scheme and slashes
<img src="https: //example.com/photo.jpg" alt="A photo">
The space after https: breaks the URL. The validator expects a / immediately after the colon but finds a space instead.
Fixed: remove the space
<img src="https://example.com/photo.jpg" alt="A photo">
Incorrect: single slash instead of double slash
<script src="https:/cdn.example.com/app.js"></script>
The URL scheme requires // after https:, but only one / is present.
Fixed: use the correct double slash
<script src="https://cdn.example.com/app.js"></script>
Incorrect: line break embedded in the URL
Sometimes copy-pasting or template rendering introduces hidden line breaks:
<video src="https://
example.com/video.mp4">
</video>
Fixed: ensure the URL is on a single line with no breaks
<video src="https://example.com/video.mp4"></video>
Incorrect: protocol-relative URL with a missing slash
<img src="/example.com/logo.png" alt="Logo">
If the intent is a protocol-relative URL, it needs two slashes. With a single slash, this becomes an absolute path on the current domain rather than a reference to example.com.
Fixed: use two slashes for protocol-relative URLs
<img src="//example.com/logo.png" alt="Logo">
Tip: If you're having trouble spotting the problem, paste the URL into your browser's address bar to see if it resolves correctly, or use a text editor that reveals invisible characters (like zero-width spaces or non-breaking spaces) that may be hiding in the string.
Every <img> element must include at least a src or a srcset attribute to be valid HTML.
The <img> element exists to embed an image into the document, and it needs to know where that image is. The src attribute provides a single URL for the image, while srcset lets you offer multiple image sources for different screen sizes or resolutions.
You might run into this error when using JavaScript to set the image source dynamically, or when using lazy-loading libraries that store the URL in a data- attribute like data-src. While those techniques work at runtime, they produce invalid HTML because the validator still expects src or srcset to be present in the markup.
If you genuinely don't have a source yet, you can use a placeholder or a transparent pixel as the src value.
Invalid Example
<img alt="A cute cat" loading="lazy" data-src="cat.jpg">
Valid Examples
Using src:
<img src="cat.jpg" alt="A cute cat">
Using srcset:
<img srcset="cat-400.jpg 400w, cat-800.jpg 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A cute cat">
Using both src and a lazy-loading data-src (keeps the markup valid while still supporting lazy loading):
<img src="placeholder.jpg" data-src="cat.jpg" alt="A cute cat">
The HTML specification mandates src on the <img> element because an image without a source has nothing to render. Omitting it produces invalid markup and unpredictable browser behavior — some browsers display a broken image icon, while others render nothing at all.
This issue commonly occurs in a few scenarios:
- Templating placeholders — A developer adds an
<img>tag intending to populate thesrcdynamically but forgets to set a default value. - Lazy loading implementations — Some lazy-loading scripts store the real URL in a
data-srcattribute and omitsrcentirely, which results in invalid HTML. - Incomplete markup — The attribute is simply forgotten during development.
How to fix it
- Add a
srcattribute with a valid URL pointing to your image. - If you're using lazy loading and want to defer the actual image source, set
srcto a lightweight placeholder (such as a tiny transparent image or a low-quality preview) and use the nativeloading="lazy"attribute instead of removingsrc.
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
<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 async attribute tells the browser to download and execute a script without blocking HTML parsing. For external scripts (those with a src attribute), this means the browser can continue parsing the page while fetching the file, then execute the script as soon as it's available. For inline module scripts (type="module"), async changes how the module's dependency graph is handled — the module and its imports execute as soon as they're all ready, rather than waiting for HTML parsing to complete.
For a classic inline script (no src, no type="module"), there is nothing to download asynchronously. The browser encounters the code directly in the HTML and executes it immediately. Applying async in this context is meaningless and contradicts the HTML specification, which is why the W3C validator flags it as an error.
Beyond standards compliance, using async incorrectly can signal a misunderstanding of script loading behavior, which may lead to bugs. For example, a developer might mistakenly believe that async on an inline script will defer its execution, when in reality it has no effect and the script still runs synchronously during parsing.
How to Fix
You have several options depending on your intent:
- If the script should be external, move the code to a separate file and reference it with the
srcattribute alongsideasync. - If the script should be an inline module, add
type="module"to the<script>tag. Note that module scripts are deferred by default, andasyncmakes them execute as soon as their dependencies are resolved rather than waiting for parsing to finish. - If the script is a plain inline script, simply remove the
asyncattribute — it has no practical effect anyway.
Examples
❌ Invalid: async on a classic inline script
<script async>
console.log("Hello, world!");
</script>
This triggers the validator error because there is no src attribute and the type is not "module".
✅ Fixed: Remove async from the inline script
<script>
console.log("Hello, world!");
</script>
✅ Fixed: Use async with an external script
<script async src="app.js"></script>
The async attribute is valid here because the browser needs to fetch app.js from the server, and async controls when that downloaded script executes relative to parsing.
✅ Fixed: Use async with an inline module
<script async type="module">
import { greet } from "./utils.js";
greet();
</script>
This is valid because module scripts have a dependency resolution phase that can happen asynchronously. The async attribute tells the browser to execute the module as soon as all its imports are resolved, without waiting for the document to finish parsing.
❌ Invalid: async with type="text/javascript" (not a module)
<script async type="text/javascript">
console.log("This is still invalid.");
</script>
Even though type is specified, only type="module" satisfies the requirement. The value "text/javascript" is the default classic script type and does not make async valid on an inline script.
The charset attribute on the <script> element tells the browser what character encoding to use when interpreting the referenced external script file. When a script is written directly inside the HTML document (an inline script), the script's character encoding is inherently the same as the document's encoding — there is no separate file to decode. Because of this, the HTML specification requires that charset only appear on <script> elements that also have a src attribute pointing to an external file.
Including charset without src violates the HTML specification and signals a misunderstanding of how character encoding works for inline scripts. Validators flag this because browsers ignore the charset attribute on inline scripts, which means it has no effect and could mislead developers into thinking they've set the encoding when they haven't.
It's also worth noting that the charset attribute on <script> is deprecated in the HTML living standard, even for external scripts. The modern best practice is to serve external script files with the correct Content-Type HTTP header (e.g., Content-Type: application/javascript; charset=utf-8) or to simply ensure all your files use UTF-8 encoding, which is the default. If you're working with an older codebase that still uses charset, consider removing it entirely and relying on UTF-8 throughout.
Examples
Incorrect: charset on an inline script
This triggers the validation error because charset is specified without a corresponding src attribute.
<script charset="utf-8">
console.log("Hello, world!");
</script>
Correct: Remove charset from inline scripts
Since inline scripts use the document's encoding, simply remove the charset attribute.
<script>
console.log("Hello, world!");
</script>
Correct: Use charset with an external script (deprecated but valid)
If you need to specify the encoding of an external script, both charset and src must be present. Note that this usage, while valid, is deprecated.
<script src="app.js" charset="utf-8"></script>
Recommended: External script without charset
The preferred modern approach is to omit charset entirely and ensure the server delivers the file with the correct encoding header, or simply use UTF-8 for everything.
<script src="app.js"></script>
The defer and async boolean attributes control how and when an external script is fetched and executed relative to HTML parsing. These attributes exist specifically to optimize the loading of external resources. An inline <script> block (one without a src attribute) doesn't need to be "downloaded" — its content is already embedded in the HTML document. Because of this, the defer attribute has no meaningful effect on inline scripts, and the HTML specification explicitly forbids this combination.
According to the WHATWG HTML living standard, the defer attribute "must not be specified if the src attribute is not present." Browsers will simply ignore the defer attribute on inline scripts, which means the script will execute synchronously as if defer were never added. This can mislead developers into thinking their inline script execution is being deferred when it isn't, potentially causing subtle timing bugs that are difficult to diagnose.
The same rule applies to the async attribute — it also requires the presence of a src attribute to be valid.
How to fix it
You have two options depending on your situation:
- If the script should be deferred, move the inline code into an external
.jsfile and reference it with thesrcattribute alongsidedefer. - If the script must remain inline, remove the
deferattribute entirely. If you need deferred execution for inline code, consider placing the<script>element at the end of the<body>, or useDOMContentLoadedto wait for the document to finish parsing.
Examples
❌ Invalid: defer on an inline script
<script defer>
console.log("hello");
</script>
This triggers the validation error because defer is present without a corresponding src attribute.
✅ Fix option 1: Add a src attribute
Move the JavaScript into an external file (e.g., app.js) and reference it:
<script defer src="app.js"></script>
The browser will download app.js in parallel with HTML parsing and execute it only after the document is fully parsed.
✅ Fix option 2: Remove defer from the inline script
If the script must stay inline, simply remove the defer attribute:
<script>
console.log("hello");
</script>
✅ Fix option 3: Use DOMContentLoaded for deferred inline execution
If you need your inline script to wait until the DOM is ready, wrap the code in a DOMContentLoaded event listener:
<script>
document.addEventListener("DOMContentLoaded", function() {
console.log("DOM is fully parsed");
});
</script>
This achieves a similar effect to defer but is valid for inline scripts.
❌ Invalid: async on an inline script
The same rule applies to async:
<script async>
document.title = "Updated";
</script>
✅ Fixed
<script async src="update-title.js"></script>
Or simply remove async if the script is inline:
<script>
document.title = "Updated";
</script>
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