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 <time> element represents a specific moment or duration in time. Browsers, search engines, and assistive technologies rely on parsing its value to understand temporal data programmatically. The element can get its machine-readable value from two places: the datetime attribute, or, if that attribute is absent, from the element's text content directly.
When there is no datetime attribute, the text content itself must be in one of the valid formats specified by the HTML standard. This is where the error typically occurs—authors write a human-readable date like "March 20, 2025" or "last Tuesday" as the text content without providing a datetime attribute, and the validator rejects it because that string isn't machine-parsable.
Why This Matters
- Machine readability: Search engines (via structured data) and browser features (like calendar integration) depend on parsing the
<time>element's value. An invalid format means these tools can't understand the date or time. - Accessibility: Screen readers and other assistive technologies may use the machine-readable datetime to present temporal information more helpfully to users.
- Standards compliance: The HTML specification explicitly defines which formats are valid. Using anything else makes your document non-conforming.
How to Fix It
You have two options:
- Add a
datetimeattribute with the machine-readable value, and keep the human-readable text as the visible content. This is the most common and practical approach. - Use a valid format directly as the text content if you don't mind displaying a machine-readable format to users.
Valid Formats
Here is a reference of accepted formats for the <time> element:
| Type | Example(s) |
|---|---|
| Valid year | 2011 |
| Valid month | 2011-11 |
| Valid date | 2011-11-18 |
| Valid yearless date | 11-18 |
| Valid week | 2011-W47 |
| Valid time | 14:54, 14:54:39, 14:54:39.929 |
| Valid local date and time | 2011-11-18T14:54:39.929 or 2011-11-18 14:54:39.929 |
| Valid global date and time | 2011-11-18T14:54:39.929Z, 2011-11-18T14:54:39.929-04:00 |
| Valid duration | PT4H18M3S, P2D (2 days), P3DT4H (3 days, 4 hours) |
Examples
Incorrect: Human-readable text without datetime attribute
The validator will report the error because "March 20, 2025" is not a valid machine-readable format:
<p>The concert is on <time>March 20, 2025</time>.</p>
Incorrect: Informal text as content
Similarly, casual date strings are not valid:
<p>Updated <time>last Friday</time>.</p>
Correct: Using the datetime attribute
Add a datetime attribute with the machine-readable value and keep the human-friendly text visible:
<p>The concert is on <time datetime="2025-03-20">March 20, 2025</time>.</p>
Correct: Including time and timezone
<p>
The event starts at
<time datetime="2025-03-20T13:00-05:00">1:00 PM EST on March 20, 2025</time>.
</p>
Correct: Machine-readable format as text content
If no datetime attribute is provided, the text content itself must be a valid format:
<p>Date: <time>2025-03-20</time></p>
Correct: Representing a duration
<p>Cooking time: <time datetime="PT1H30M">1 hour and 30 minutes</time>.</p>
Correct: Using just a time value
<p>The shop opens at <time>09:00</time> every day.</p>
As a general rule, whenever you want to display a date or time in a natural, human-friendly way, always pair it with a datetime attribute that contains the machine-readable equivalent. This keeps your HTML valid, your content accessible, and your temporal data useful to machines.
The aria-checked attribute is redundant on <input type="radio"> because the browser already exposes the checked state to assistive technologies through the native checked property.
Radio inputs have built-in semantics that map directly to the aria-checked state. When a radio button is selected, the browser automatically communicates that state to the accessibility tree. Adding aria-checked manually creates a conflict: the attribute could fall out of sync with the actual checked state, and it overrides the native semantics that assistive technologies already understand.
The ARIA in HTML specification explicitly prohibits aria-checked on <input type="radio">. This falls under the general principle of not using ARIA attributes to duplicate what HTML already provides natively.
If you need to create custom styled radio buttons that don't use native <input> elements, then aria-checked belongs on an element with role="radio". But when using a standard <input type="radio">, the checked attribute (or the DOM property) is all you need.
Invalid example
<label>
<input type="radio" name="color" value="red" aria-checked="true" checked>
Red
</label>
<label>
<input type="radio" name="color" value="blue" aria-checked="false">
Blue
</label>
Valid example
Remove aria-checked and rely on the native checked attribute:
<label>
<input type="radio" name="color" value="red" checked>
Red
</label>
<label>
<input type="radio" name="color" value="blue">
Blue
</label>
If you need a custom radio group without native inputs, use role="radio" with aria-checked:
<div role="radiogroup" aria-label="Color">
<span role="radio" aria-checked="true" tabindex="0">Red</span>
<span role="radio" aria-checked="false" tabindex="-1">Blue</span>
</div>
The aria-expanded attribute is redundant on a summary element when it is the direct child of a details element, because the browser already communicates the expanded/collapsed state through the native open attribute on details.
The summary element, when used as the first child of a details element, acts as the built-in toggle control. Assistive technologies already understand this relationship and automatically convey whether the disclosure widget is open or closed based on the details element's open attribute. Adding aria-expanded to summary in this context creates duplicate semantics, which can confuse screen readers by announcing the state twice.
If you're using JavaScript to toggle aria-expanded manually, you can safely remove it and rely on the native behavior instead. The details/summary pattern is one of the best examples of built-in accessibility that requires no extra ARIA attributes.
Incorrect Example
<details>
<summary aria-expanded="false">More information</summary>
<p>Here are the additional details.</p>
</details>
Correct Example
<details>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
If you need the section to be open by default, use the open attribute on the details element:
<details open>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
HTML has a defined set of global attributes (like class, id, title, lang, etc.) and element-specific attributes that are permitted by the specification. Any attribute that falls outside this set — such as st_url, st_title, or displayText — will trigger a validation error because the browser and the validator don't recognize it as part of the HTML standard.
The st_url attribute, along with related attributes like st_title, st_via, and displayText, originated from early versions of the ShareThis social sharing library. These were proprietary attributes that the ShareThis JavaScript would read from the DOM to configure sharing behavior. While browsers generally ignore attributes they don't understand (so the page may still appear to work), using non-standard attributes violates the HTML specification and can cause several problems:
- Standards compliance: Invalid HTML can lead to unpredictable behavior across different browsers and future browser versions.
- Accessibility: Screen readers and other assistive technologies may not handle non-standard attributes correctly, potentially causing confusion.
- Maintainability: Non-standard markup is harder for other developers to understand and maintain.
- SEO: Search engine crawlers may penalize or misinterpret pages with significant validation errors.
HTML5 introduced data-* attributes specifically to solve this problem. Any custom data you need to attach to an element can use this pattern — for example, data-st-url instead of st_url. The ShareThis library itself updated its integration to use data-* attributes in later versions, so modern implementations should already follow this pattern.
How to Fix
- Replace proprietary attributes with
data-*equivalents: Convertst_urltodata-st-url,st_titletodata-st-title,displayTexttodata-display-text, and so on. - Update your ShareThis integration: If you're using an outdated version of ShareThis (or an outdated CMS module/plugin like an old Drupal integration), update to the latest version, which uses valid HTML5 attributes.
- Check your CMS templates: If the invalid attributes are hardcoded in a theme template or a content block, update them manually.
Examples
Invalid: Proprietary attributes on a <span>
<span class="st_facebook_large" displayText="Facebook" st_url="https://example.com" st_title="My Page Title"></span>
<span class="st_twitter_large" displayText="Twitter" st_url="https://example.com" st_title="My Page Title"></span>
This markup uses st_url, st_title, and displayText, none of which are valid HTML attributes.
Valid: Using data-* attributes instead
<span class="st_facebook_large" data-display-text="Facebook" data-st-url="https://example.com" data-st-title="My Page Title"></span>
<span class="st_twitter_large" data-display-text="Twitter" data-st-url="https://example.com" data-st-title="My Page Title"></span>
By prefixing each custom attribute with data-, the markup becomes valid HTML5. Note that you may also need to update the associated JavaScript to read from these new attribute names (e.g., using element.dataset.stUrl instead of element.getAttribute('st_url')).
Valid: Modern ShareThis integration
If you're updating your ShareThis integration entirely, the modern approach uses a different markup pattern:
<div class="sharethis-inline-share-buttons" data-url="https://example.com" data-title="My Page Title"></div>
Modern versions of the ShareThis library expect data-* attributes by default, so upgrading the library and its associated markup is the cleanest solution. Check the ShareThis documentation for the latest recommended integration approach for your platform.
HTML has a defined set of global attributes (such as id, class, lang, and title) and element-specific attributes. Any attribute that isn't part of these recognized sets will trigger a validation error. The st_title attribute is a proprietary, non-standard attribute that was used by older versions of the ShareThis sharing widget to pass metadata — specifically, the title of the content being shared.
The HTML5 specification introduced data-* attributes as the standard mechanism for embedding custom data on elements. These attributes allow developers to store arbitrary information without conflicting with the HTML spec. Newer versions of ShareThis and similar services have adopted this convention, but legacy code — especially in CMS themes, plugins, or modules — may still use the old non-standard format.
Using invalid attributes causes several problems:
- Standards compliance: The document fails W3C validation, which can indicate deeper markup quality issues.
- Future compatibility: Browsers are not required to handle non-standard attributes in any predictable way. Future browser updates could ignore or strip them.
- Maintainability: Non-standard attributes make code harder for other developers to understand and maintain.
- Accessibility tools: Screen readers and other assistive technologies rely on well-formed HTML. Invalid attributes can cause unexpected behavior in these tools.
To fix this, replace all proprietary ShareThis attributes with their data-* equivalents. For example, st_title becomes data-st-title, st_url becomes data-st-url, and displayText becomes data-st-displaytext. You should also update the ShareThis JavaScript library to a version that recognizes the new attribute format.
Examples
❌ Invalid: Using proprietary attributes
<span class="st_sharethis" st_title="My Article" st_url="https://example.com/article" displayText="ShareThis">
Share
</span>
This triggers validation errors for st_title, st_url, and displayText because none of these are valid HTML attributes.
✅ Valid: Using data-* attributes
<span class="st_sharethis" data-st-title="My Article" data-st-url="https://example.com/article" data-st-displaytext="ShareThis">
Share
</span>
All custom data is now stored in properly namespaced data-* attributes, which are fully compliant with the HTML5 specification.
✅ Valid: Using a button element with data-* attributes
If the element is interactive (e.g., it triggers a share action on click), consider using a <button> instead of a <span> for better accessibility:
<button type="button" class="st_sharethis" data-st-title="My Article" data-st-url="https://example.com/article">
Share this article
</button>
Fixing this in a CMS
If you're using Drupal, WordPress, or another CMS with a ShareThis module or plugin:
- Update the plugin/module to the latest version — most have already migrated to
data-*attributes. - Check your theme templates for hardcoded ShareThis markup that may still use the old attribute format.
- Search your codebase for
st_title,st_url, anddisplayTextand replace them withdata-st-title,data-st-url, anddata-st-displaytextrespectively. - Update the ShareThis JavaScript to a version compatible with the new attribute names, and verify that sharing functionality still works after the change.
The correct value is nowrap (without a hyphen), not no-wrap.
The flex-wrap CSS property controls whether flex items are forced onto a single line or can wrap onto multiple lines. It accepts three values: nowrap (the default), wrap, and wrap-reverse. A common mistake is writing no-wrap with a hyphen, likely because the white-space CSS property uses nowrap and no-wrap interchangeably in some contexts, or simply because it looks more natural in English. However, for flex-wrap, only the unhyphenated nowrap is valid.
HTML Example With the Issue
<div style="display: flex; flex-wrap: no-wrap;">
<p>Item 1</p>
<p>Item 2</p>
</div>
Fixed HTML Example
<div style="display: flex; flex-wrap: nowrap;">
<p>Item 1</p>
<p>Item 2</p>
</div>
The min-height property sets the minimum height of an element. Unlike shorthand properties such as margin or padding, min-height accepts only a single value. Providing multiple space-separated values (e.g., min-height: 100px 200px) is invalid and will trigger this error.
This error commonly occurs for several reasons:
- Multiple values provided:
min-heightis not a shorthand and does not accept more than one value. - Invalid units or typos: Using an unrecognized unit (e.g.,
100pixelsinstead of100px) or a misspelled keyword. - Using unsupported CSS functions or syntax: Some newer CSS features like
min-height: fit-content(200px)may not be recognized by the validator or may lack browser support. - Confusing
min-heightwith other properties: Accidentally using syntax meant for properties likegrid-template-rowsorminmax()expressions. - Missing units on non-zero values: Writing
min-height: 100instead ofmin-height: 100px. Zero is the only numeric value that doesn't require a unit.
According to the CSS specification, valid values for min-height include:
| Value Type | Examples |
|---|---|
| Length | 0, 100px, 10em, 5rem, 50vh |
| Percentage | 50%, 100% |
| Keywords | auto, min-content, max-content, none |
| Functions | fit-content, calc(100vh - 50px) |
Fixing this issue ensures your CSS is standards-compliant and behaves predictably across browsers. Invalid min-height values will be ignored by browsers, which means your layout may not render as intended.
Examples
Incorrect: multiple values
<div style="min-height: 100px 200px;">Content</div>
min-height only accepts a single value. This is not a shorthand property.
Incorrect: missing unit
<div style="min-height: 100;">Content</div>
Non-zero numeric values must include a unit.
Incorrect: invalid keyword or typo
<div style="min-height: inheret;">Content</div>
The keyword inherit is misspelled.
Correct: single length value
<div style="min-height: 100px;">Content</div>
Correct: percentage value
<div style="min-height: 50%;">Content</div>
Correct: using calc() for computed values
<div style="min-height: calc(100vh - 80px);">Content</div>
Correct: using a keyword
<div style="min-height: min-content;">Content</div>
Correct: using auto
<div style="min-height: auto;">Content</div>
If you need to set both a minimum and maximum height on an element, use min-height and max-height as separate properties:
<div style="min-height: 100px; max-height: 400px;">Content</div>
HTML documents must use UTF-8 as their character encoding. The legacy encoding iso-8859-15 is no longer allowed in modern HTML.
The HTML living standard requires all documents to be encoded in UTF-8. Older encodings like iso-8859-15 (also known as Latin-9) were common in the past, especially for Western European languages, but they are now considered legacy. UTF-8 supports virtually all characters from every writing system, making it the universal standard for the web.
To fix this, you need to do two things. First, update the <meta> charset declaration in your HTML to specify UTF-8. Second — and this is the important part — you must actually save or convert the file itself to UTF-8 encoding. Simply changing the meta tag without re-encoding the file can cause characters like é, ñ, or € to display incorrectly.
Most modern code editors (VS Code, Sublime Text, Notepad++) let you change the file encoding. In VS Code, click the encoding label in the bottom status bar and choose "Save with Encoding" → "UTF-8".
If your server is sending an iso-8859-15 charset in the HTTP Content-Type header, you'll also need to update that. The HTTP header takes precedence over the meta tag.
HTML Examples
❌ Incorrect: legacy encoding
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="iso-8859-15">
<title>Mon site</title>
</head>
<body>
<p>Bienvenue sur mon site € 2025</p>
</body>
</html>
✅ Correct: UTF-8 encoding
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Mon site</title>
</head>
<body>
<p>Bienvenue sur mon site € 2025</p>
</body>
</html>
If you're using an Apache server, update your .htaccess or server config:
AddDefaultCharset UTF-8
For Nginx, update the server block:
charset utf-8;
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 value inter-ideograph is not part of the CSS text-justify specification and will not pass validation.
The text-justify property controls how the browser distributes spacing when text-align is set to justify. The value inter-ideograph was an Internet Explorer proprietary extension that added extra spacing between ideographic (CJK) characters and words. It was never standardized by the W3C and is not recognized by modern browsers.
The valid values for text-justify in the current CSS Text Module Level 3 specification are:
auto— the browser chooses the best justification method based on the content language. For CJK text, most browsers already apply inter-ideograph-style spacing automatically with this value.inter-word— spacing is distributed between words only (best for Latin scripts).inter-character— spacing is distributed between characters (useful for CJK text).none— disables justification.
For content that mixes CJK and Latin text, auto is usually the best choice, since the browser will apply appropriate spacing rules for each script.
HTML examples
Before (invalid)
<p style="text-align: justify; text-justify: inter-ideograph;">
This text uses a non-standard value.
</p>
After (valid)
<p style="text-align: justify; text-justify: auto;">
This text uses a valid value. The browser applies
appropriate justification based on the content language.
</p>
The http-equiv attribute on a <meta> element simulates an HTTP response header, allowing you to define document-level metadata that would otherwise require server configuration. Because this metadata applies to the entire document and must be processed before the page content is rendered, the HTML specification requires that <meta http-equiv> elements appear within the <head> element. Placing them in the <body> is invalid and may cause browsers to ignore them entirely, leading to unexpected behavior like incorrect character encoding, broken content security policies, or missing refresh directives.
This error commonly occurs when:
- A
<meta http-equiv>tag is accidentally placed inside<body>. - Content is copy-pasted from one document into the body of another, bringing along
<meta>tags. - A templating system or CMS injects
<meta>tags in the wrong location.
Common http-equiv values
The http-equiv attribute supports several standard values:
content-type— Declares the document's MIME type and character encoding. In HTML5, the shorthand<meta charset="UTF-8">is preferred instead.refresh— Instructs the browser to reload the page or redirect after a specified number of seconds. Note that automatic refreshing is discouraged for accessibility reasons: it can disorient users, move focus back to the top of the page, and disrupt assistive technology. Avoid it unless absolutely necessary.content-security-policy— Defines a Content Security Policy for the document, helping prevent cross-site scripting (XSS) and other code injection attacks.default-style— Specifies the preferred stylesheet from a set of alternative stylesheets.
How to fix it
Find every <meta http-equiv> element in your document and ensure it is placed inside the <head> element, before any content in <body>. If the tag is duplicated or unnecessary, remove it.
Examples
❌ Incorrect: http-equiv inside <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<p>Hello, world!</p>
</body>
</html>
The <meta http-equiv> tag is inside <body>, which triggers the validation error.
✅ Correct: http-equiv inside <head>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: using the HTML5 charset shorthand
In HTML5, you can replace <meta http-equiv="content-type" content="text/html; charset=UTF-8"> with the simpler charset attribute:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: Content Security Policy in <head>
<head>
<meta charset="UTF-8">
<meta http-equiv="content-security-policy" content="default-src 'self'">
<title>Secure Page</title>
</head>
The content-security-policy value is particularly placement-sensitive — browsers will ignore it if it appears outside <head>, leaving your page without the intended security protections.
The autocomplete attribute tells the browser whether it can assist the user in filling out a form field, and if so, what type of data is expected. The generic values "on" and "off" control whether the browser should offer autofill suggestions to the user. Since type="hidden" inputs are never displayed and never receive direct user input, these values don't apply — there's no user interaction to assist with.
According to the HTML specification, hidden inputs can have an autocomplete attribute, but only with specific named autofill detail tokens (like "transaction-id" or "cc-number"). These tokens serve a programmatic purpose by providing hints about the semantic meaning of the hidden value, which can be useful for form processing. The generic "on" and "off" values, however, are explicitly disallowed because they only relate to the user-facing autofill behavior.
This validation error matters for standards compliance and can indicate a logical mistake in your markup. If you added autocomplete="off" to a hidden input hoping to prevent the browser from caching or modifying the value, it won't have that effect. Hidden input values are controlled entirely by the server or by JavaScript, not by browser autofill.
How to fix it
- Remove the
autocompleteattribute if it's not needed — this is the most common fix. - Use a specific autofill token if you need to convey semantic meaning about the hidden value (e.g.,
autocomplete="transaction-id"). - Reconsider the input type — if the field genuinely needs autofill behavior controlled, it probably shouldn't be
type="hidden".
Examples
Incorrect: using autocomplete="off" on a hidden input
<form action="/submit" method="post">
<input type="hidden" name="token" value="abc123" autocomplete="off">
<button type="submit">Submit</button>
</form>
Incorrect: using autocomplete="on" on a hidden input
<form action="/submit" method="post">
<input type="hidden" name="session-id" value="xyz789" autocomplete="on">
<button type="submit">Submit</button>
</form>
Correct: removing the autocomplete attribute
<form action="/submit" method="post">
<input type="hidden" name="token" value="abc123">
<button type="submit">Submit</button>
</form>
Correct: using a specific autofill token
If the hidden input carries a value with a well-defined autofill semantic, you can use a named token:
<form action="/checkout" method="post">
<input type="hidden" name="txn" value="TXN-001" autocomplete="transaction-id">
<button type="submit">Complete Purchase</button>
</form>
This is valid because "transaction-id" is a specific autofill detail token recognized by the specification, unlike the generic "on" or "off" values.
The xmlns:sodipodi attribute is a namespace declaration left over from the Inkscape SVG editor and is not valid in HTML5 documents.
When you embed an SVG directly in HTML, the HTML5 parser only recognizes a limited set of namespace declarations. The xmlns:sodipodi namespace (along with others like xmlns:inkscape) is specific to Inkscape's internal SVG format and serves no purpose in the browser. The W3C validator flags it because it cannot be serialized as XML 1.0 within the HTML5 context.
Inkscape adds several custom namespaces and attributes to SVG files for its own editing purposes. These include sodipodi:* and inkscape:* namespaces, along with their associated attributes and elements. Browsers simply ignore them, so removing them has no effect on how the SVG renders.
The fix is straightforward: remove the xmlns:sodipodi declaration from the <svg> tag, and also remove any sodipodi:* attributes or <sodipodi:*> elements throughout the SVG.
HTML Examples
❌ Invalid: SVG with Inkscape namespaces
<svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 100 100"
sodipodi:docname="icon.svg">
<sodipodi:namedview pagecolor="#ffffff" />
<circle cx="50" cy="50" r="40" fill="blue"
inkscape:label="Main Circle" />
</svg>
✅ Valid: Cleaned SVG without editor-specific namespaces
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
If you frequently export SVGs from Inkscape, consider using the "Plain SVG" export option (File → Save As → Plain SVG) instead of the default "Inkscape SVG" format. You can also use tools like SVGO or SVG Cleaner to automatically strip editor metadata before embedding.
HTML5 uses a specific serialization format that is distinct from XML. While XML allows you to declare arbitrary namespace prefixes using xmlns:prefix attributes, the HTML parser does not recognize or process these declarations. The HTML specification only supports a small set of predefined namespaces — the default HTML namespace, SVG (http://www.w3.org/2000/svg), MathML (http://www.w3.org/1998/Math/MathML), XLink, XML, and XMLNS — and these are handled implicitly through the appropriate embedding elements (<svg>, <math>), not through explicit xmlns: declarations.
The phrase "not serializable as XML 1.0" in the validator message means that this attribute cannot be round-tripped between the HTML parser and an XML serializer. The HTML parser treats xmlns:w as an opaque attribute name containing a colon, rather than as a namespace declaration. If you were to serialize this DOM back to XML, the result would be invalid because xmlns:w would be interpreted as a namespace declaration for a prefix that may conflict with actual element usage.
This issue almost always originates from content exported or pasted from Microsoft Word, Excel, or other Office applications. These tools generate markup laden with proprietary namespace declarations like xmlns:w, xmlns:o (Office), xmlns:v (VML), and xmlns:x (Excel). Along with these declarations come Office-specific elements and attributes (e.g., <w:WordDocument>, <o:OfficeDocumentSettings>) that have no meaning in a web browser and bloat your HTML.
Beyond being a validation error, leaving this markup in place creates several problems. It adds significant unnecessary weight to your pages, makes the source code harder to read and maintain, and signals that the HTML was auto-generated without cleanup — which can affect long-term maintainability. Browsers silently ignore these attributes and elements, so they provide no functional benefit.
How to fix it
- Remove all
xmlns:prefixed attributes from your HTML elements, includingxmlns:w,xmlns:o,xmlns:v,xmlns:x, and any others. - Remove any Office-specific elements that use those namespace prefixes, such as
<o:p>,<w:WordDocument>, or<v:shapetype>. These elements are meaningless in HTML5. - Clean up associated conditional comments like
<!--[if gte mso 9]>that often wrap Office-specific blocks. - Consider using a paste-cleanup tool if you regularly paste content from Word into your HTML. Many CMS platforms and text editors offer "paste as plain text" or "clean HTML" options.
If you genuinely need to work with Office Open XML namespaces, use an appropriate XML-based format (XHTML served as application/xhtml+xml, or OOXML documents) rather than standard HTML5.
Examples
Incorrect — Office namespace declarations in HTML
This markup is typical of content saved or exported from Microsoft Word:
<html xmlns:w="urn:schemas-microsoft-com:office:word"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Report</title>
<!--[if gte mso 9]>
<w:WordDocument>
<w:View>Normal</w:View>
</w:WordDocument>
<![endif]-->
</head>
<body>
<p class="MsoNormal">Hello world<o:p></o:p></p>
</body>
</html>
Correct — Clean HTML without Office markup
<!DOCTYPE html>
<html lang="en">
<head>
<title>Report</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Incorrect — Single namespace on the <html> element
Even a single custom namespace declaration triggers the error:
<html xmlns:w="urn:schemas-microsoft-com:office:word">
<head>
<title>My Page</title>
</head>
<body>
<p>Content here.</p>
</body>
</html>
Correct — Remove the attribute entirely
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Content here.</p>
</body>
</html>
Note that the default xmlns="http://www.w3.org/1999/xhtml" attribute on the <html> element is permitted (though unnecessary in HTML5), but any prefixed namespace declaration like xmlns:w is not.
When graphic design tools like Affinity Designer (formerly Serif) export SVG files, they often embed custom namespace declarations such as xmlns:serif="http://www.serif.com/". These namespaces allow the editor to store its own metadata — like layer names, grouping information, or application-specific settings — inside the SVG file. While this metadata is useful if you re-open the file in the original editor, it has no meaning in a web browser.
The HTML5 specification defines a specific set of namespace attributes that are allowed in SVG elements (such as xmlns, xmlns:xlink, and xmlns:xml). Any namespace prefix not in this predefined list — like xmlns:serif, xmlns:inkscape, or xmlns:sodipodi — triggers this validation error because the HTML parser cannot serialize these attributes back into well-formed XML 1.0. This isn't just a theoretical concern: non-serializable attributes can cause issues when the DOM is manipulated via JavaScript or when the markup is processed by XML-based tools.
Beyond the xmlns:serif declaration itself, you'll likely find attributes in the SVG that use this namespace prefix, such as serif:id="layer1". These should also be removed since they reference a namespace the browser doesn't understand.
How to Fix It
- Remove the
xmlns:serifattribute from the<svg>element. - Remove any attributes prefixed with
serif:(e.g.,serif:id) from child elements within the SVG. - If you re-export the SVG, check your editor's export settings — some tools offer a "clean" or "optimized" export option that strips proprietary metadata.
- Consider using an SVG optimization tool like SVGO to automatically clean up unnecessary attributes.
Examples
❌ Invalid: SVG with xmlns:serif attribute
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:serif="http://www.serif.com/"
viewBox="0 0 100 100"
width="100"
height="100">
<g serif:id="Layer 1">
<circle cx="50" cy="50" r="40" fill="blue" />
</g>
</svg>
This triggers the error because xmlns:serif is not a recognized namespace in HTML5, and serif:id references that unsupported namespace.
✅ Valid: SVG with proprietary attributes removed
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
width="100"
height="100">
<g>
<circle cx="50" cy="50" r="40" fill="blue" />
</g>
</svg>
Both xmlns:serif and serif:id have been removed. The SVG renders identically in the browser since those attributes were only meaningful to the editing application.
Handling Multiple Proprietary Namespaces
Exported SVGs sometimes contain several non-standard namespaces at once. Remove all of them:
<!-- ❌ Invalid: multiple proprietary namespaces -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:serif="http://www.serif.com/"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 200 200">
<rect x="10" y="10" width="180" height="180" fill="red"
serif:id="background"
inkscape:label="bg-rect" />
</svg>
<!-- ✅ Valid: all proprietary namespaces and prefixed attributes removed -->
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 200 200">
<rect x="10" y="10" width="180" height="180" fill="red" />
</svg>
If you frequently work with SVGs from design tools, integrating an SVG optimizer into your build process can save time and ensure these non-standard attributes never reach production.
The grid-template-rows property defines the size of each row in a CSS grid layout. The W3C validator checks that every value in the declaration conforms to the CSS Grid specification. When you see this error, the validator has encountered something it cannot parse as a valid track size.
Common causes of this error include:
- Typos or invalid units — writing
100 px(with a space),100pixels, or1 frinstead of1fr. - Using values from other properties — for example,
flex,inline, orspace-betweenare not valid row track sizes. - Incorrect function syntax — missing commas in
repeat(), providing wrong arguments tominmax(), or using unsupported functions. - Missing units — writing a bare number like
100instead of100px(zero is the only number that doesn't require a unit). - Using newer syntax not yet recognized — some cutting-edge features like
subgridor themasonryvalue may trigger validation warnings depending on the validator's supported spec level.
The grid-template-rows property accepts these valid value types:
- Length values:
100px,5em,10rem,20vh - Percentages:
50%,33.3% - Flexible lengths:
1fr,2fr - Keywords:
auto,min-content,max-content,none - Functions:
repeat(),minmax(),fit-content() - Named lines:
[row-start] 100px [row-end]
This matters for standards compliance and forward compatibility. While browsers may be lenient and ignore invalid values, relying on that behavior can lead to layouts that silently break. Valid CSS ensures your grid behaves predictably across all browsers.
Examples
Incorrect — invalid values
<style>
/* ERROR: "full" is not a valid track size */
.grid-a {
display: grid;
grid-template-rows: full auto;
}
/* ERROR: space between number and unit */
.grid-b {
display: grid;
grid-template-rows: 100 px 200 px;
}
/* ERROR: bare number without a unit */
.grid-c {
display: grid;
grid-template-rows: 100 200;
}
/* ERROR: missing comma in repeat() */
.grid-d {
display: grid;
grid-template-rows: repeat(3 1fr);
}
</style>
Correct — valid track sizes
<style>
/* Fixed pixel heights */
.grid-a {
display: grid;
grid-template-rows: 100px auto;
}
/* Flexible units */
.grid-b {
display: grid;
grid-template-rows: 1fr 2fr;
}
/* Repeat function with correct syntax */
.grid-c {
display: grid;
grid-template-rows: repeat(3, 1fr);
}
/* Minmax with auto */
.grid-d {
display: grid;
grid-template-rows: minmax(100px, 1fr) auto;
}
</style>
Full working example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Grid template rows example</title>
<style>
.grid-container {
display: grid;
grid-template-rows: 80px minmax(150px, 1fr) auto;
gap: 8px;
height: 400px;
}
.grid-container > div {
background: #e0e0e0;
padding: 16px;
}
</style>
</head>
<body>
<div class="grid-container">
<div>Row 1 — fixed 80px</div>
<div>Row 2 — between 150px and 1fr</div>
<div>Row 3 — auto-sized to content</div>
</div>
</body>
</html>
Using fit-content() and named lines
<style>
.grid {
display: grid;
grid-template-rows: [header] fit-content(100px) [main] 1fr [footer] auto;
}
</style>
If your value looks correct but the validator still flags it, check whether you're using a very new CSS feature like subgrid or masonry. These may not yet be recognized by the validator even if some browsers support them. In that case, the warning can be acknowledged while keeping the value intentionally.
An element with a tabindex attribute is interactive and focusable by the user, which conflicts with role="none" (or its synonym role="presentation"), because that role tells assistive technologies to ignore the element's semantics.
When a browser encounters this conflict, it ignores the role="none" declaration and keeps the element's native semantics. The element remains in the tab order and is announced normally by screen readers. The role="none" has no effect, so the code is misleading.
The role="none" role strips an element of its implicit ARIA semantics. A <table role="none">, for example, is no longer announced as a table. But the HTML spec requires browsers to preserve semantics on any element that is focusable. An element with tabindex is focusable by definition, so the two attributes contradict each other.
To fix this, decide what the element should actually do. If the element should be focusable and interactive, remove role="none". If the element should have no semantics and no interaction, remove tabindex.
Example with the issue
<div role="none" tabindex="0">
Click me
</div>
The validator warns that role="none" is ignored here because tabindex="0" makes the element focusable.
Fixed examples
Remove role="none" if the element should remain focusable:
<div tabindex="0">
Click me
</div>
Or remove tabindex if the element should truly have no semantics and no keyboard interaction:
<div role="none">
Click me
</div>
The <area> element defines a clickable region within an image map (<map>). The coords attribute works together with the shape attribute to describe the geometry of that region. When the coordinates don't conform to the rules for the given shape, the browser may ignore the area entirely or interpret it unpredictably, making the clickable region inaccessible to users.
Each shape type has strict requirements:
- Rectangle (
shape="rect"): Requires exactly four integers in the formatx1,y1,x2,y2, wherex1,y1is the top-left corner andx2,y2is the bottom-right corner. Because0,0is the top-left of the image,x1must be less thanx2andy1must be less thany2. - Circle (
shape="circle"): Requires exactly three integers in the formatx,y,r, wherex,yis the center of the circle andris the radius. The radius must be a positive integer, and the first coordinate (the x-center) must be less than the third value (the radius) is not required—but the validator message mentions this constraint to flag cases where values appear swapped or malformed. - Polygon (
shape="poly"): Requires at least six integers (threex,ycoordinate pairs), forming a polygon with at least three vertices (a triangle). The format isx1,y1,x2,y2,...,xn,yn, and the number of integers must be even since they represent pairs.
Getting these formats wrong is a standards compliance issue. Assistive technologies such as screen readers rely on valid <area> definitions to convey interactive regions to users. Invalid coordinates can also cause the clickable area to silently fail in some browsers.
Examples
Invalid: Rectangle with swapped coordinates
The top-left corner values are larger than the bottom-right corner values:
<map name="nav">
<area shape="rect" coords="200,150,50,10" href="/home" alt="Home">
</map>
Fixed: Rectangle with correct coordinate order
<map name="nav">
<area shape="rect" coords="50,10,200,150" href="/home" alt="Home">
</map>
Invalid: Circle with wrong number of values
Four values are provided instead of the required three:
<map name="nav">
<area shape="circle" coords="100,75,50,25" href="/info" alt="Info">
</map>
Fixed: Circle with three values
<map name="nav">
<area shape="circle" coords="100,75,50" href="/info" alt="Info">
</map>
Invalid: Polygon with too few coordinates
Only four integers (two coordinate pairs) are provided, but a polygon needs at least three pairs:
<map name="nav">
<area shape="poly" coords="10,20,30,40" href="/about" alt="About">
</map>
Fixed: Polygon with at least three coordinate pairs
<map name="nav">
<area shape="poly" coords="10,20,30,40,20,60" href="/about" alt="About">
</map>
Invalid: Non-integer or malformed values
Decimal numbers and spaces in the wrong places will also trigger this error:
<map name="nav">
<area shape="rect" coords="10.5, 20, 100, 200" href="/page" alt="Page">
</map>
Fixed: Using only comma-separated integers
<map name="nav">
<area shape="rect" coords="10,20,100,200" href="/page" alt="Page">
</map>
Complete valid image map example
<img src="floorplan.png" alt="Office floor plan" usemap="#office">
<map name="office">
<area shape="rect" coords="0,0,150,100" href="/lobby" alt="Lobby">
<area shape="circle" coords="200,150,40" href="/meeting-room" alt="Meeting room">
<area shape="poly" coords="300,50,400,50,400,150,350,200,300,150" href="/lounge" alt="Lounge">
</map>
When debugging coordinate issues, double-check that the shape attribute matches the number of coordinates you've provided, that all values are non-negative integers separated by commas with no extra spaces, and that rectangle corners are specified in the correct top-left to bottom-right order.
Custom namespace declarations like xmlns:rdf are not serializable as XML 1.0 when used inside HTML5 documents.
In HTML5 (the text/html serialization), the xmlns attribute is only supported in a very limited way. You can use xmlns to declare the default namespace on the <html> or <svg> element, but you cannot declare additional namespace prefixes like xmlns:rdf, xmlns:dc, or xmlns:cc. These prefixed namespace declarations are a feature of XML and XHTML, and the HTML5 parser simply treats them as regular attributes — which then fail XML 1.0 serialization rules.
This commonly happens when SVG files are exported from tools like Inkscape, which embed RDF metadata with namespace declarations. When you paste or include such SVGs directly in an HTML5 document, the validator flags these attributes.
The fix is straightforward: remove the unsupported namespace declarations and any elements that depend on them. If the SVG contains <rdf:RDF>, <cc:Work>, or <dc:title> blocks, those can be safely removed without affecting the visual rendering of the SVG.
HTML Examples
❌ Invalid: SVG with unsupported namespace declarations
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
viewBox="0 0 100 100">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>My Icon</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
✅ Valid: SVG with namespace declarations and metadata removed
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
If you need to preserve metadata, consider adding it outside the SVG using standard HTML elements like <meta> tags or structured data formats such as JSON-LD.
The <link> element defines a relationship between the current document and an external resource — most commonly stylesheets, icons, preloaded assets, or canonical URLs. According to the HTML specification, the element must include at least one of the href or imagesrcset attributes so the browser knows what resource is being linked. A <link> element without either attribute is essentially an empty declaration: it tells the browser about a relationship type (via rel) but provides no actual resource to fetch or reference.
This validation error commonly occurs in a few scenarios:
- Templating or CMS issues: A dynamic template generates a
<link>tag but the URL variable is empty or undefined, resulting in a bare element with nohref. - Incomplete code: A developer adds a
<link>with arelattribute intending to fill in thehreflater but forgets to do so. - Copy-paste mistakes: Attributes are accidentally removed during editing or refactoring.
Fixing this is important for several reasons. Browsers may ignore the element entirely or behave unpredictably when encountering a <link> with no resource URL. Unnecessary elements without purpose add bloat to the document and can confuse other developers reading the code. Additionally, tools that parse HTML — such as search engine crawlers and assistive technologies — rely on well-formed markup to correctly understand document relationships.
To resolve the issue, add an href attribute pointing to the target resource, use an imagesrcset attribute when providing responsive image sources, or include both when appropriate.
Examples
Invalid: missing both href and imagesrcset
This <link> declares a stylesheet relationship but doesn't specify where the stylesheet is located:
<link rel="stylesheet">
This preload link has an as attribute but no resource to fetch:
<link rel="preload" as="image" type="image/png">
Fixed: using href
The most common fix is adding an href attribute with a valid URL:
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="canonical" href="https://example.com/page">
Fixed: using imagesrcset
For responsive image preloading, the imagesrcset attribute specifies multiple image sources at different resolutions. This is valid without href:
<link rel="preload" as="image" type="image/png" imagesrcset="icon-1x.png 1x, icon-2x.png 2x">
Fixed: using both href and imagesrcset
You can combine both attributes. The href serves as a fallback resource while imagesrcset provides responsive alternatives:
<link rel="preload" as="image" href="icon-1x.png" imagesrcset="icon-1x.png 1x, icon-2x.png 2x">
Handling dynamic templates
If your <link> elements are generated dynamically, make sure the element is only rendered when a valid URL is available. For example, in a template, wrap the output in a conditional check rather than outputting an empty <link>:
<!-- Bad: outputs a link even when the URL is empty -->
<link rel="stylesheet" href="">
<!-- Good: only include the element when there's a real URL -->
<link rel="stylesheet" href="styles.css">
Note that href="" resolves to the current page URL and is technically valid syntax (it won't trigger this specific error), but it's almost certainly not what you intend. Always ensure the href value points to the correct resource.
The HTML specification 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
<img src="" alt="Profile photo">
This triggers the validation error because the src value is an empty string.
❌ Bad: src with only whitespace
<img src=" " alt="Profile photo">
Whitespace-only values are also considered invalid and will produce a similar error.
✅ Good: valid image path
<img src="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:
<img src="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 -->
<img src="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 is attribute on the <a> element is present but set to an empty string. This attribute names a customized built-in element, so it must hold a valid custom element name and cannot be empty.
The is attribute tells the browser to upgrade a standard element into a customized built-in element. You register one in JavaScript with customElements.define("fancy-link", FancyLink, { extends: "a" }), then write <a is="fancy-link"> to apply it. The value has to match a registered custom element name, which is always non-empty and contains a hyphen. An empty is="" names nothing, so the validator rejects it.
In practice this usually comes from a template that prints is="" when the variable feeding it is blank. If the element is not meant to be a customized built-in, remove the is attribute entirely. If it is, set the name of the element you registered.
Note that Safari does not support customized built-in elements, so many projects avoid is and use autonomous custom elements (a hyphenated tag such as <fancy-link>) instead.
Invalid example
<a is="" href="/pricing">Pricing</a>
Valid example
Remove is when the link is an ordinary anchor:
<a href="/pricing">Pricing</a>
Or give it the name of a registered customized built-in element:
<a is="fancy-link" href="/pricing">Pricing</a>
The role="presentation" on this element is ignored because the element also carries a global ARIA attribute, such as aria-label or aria-describedby.
role="presentation" (and its synonym role="none") removes an element's implicit semantics so assistive technologies treat it as if it were plain content. Global ARIA states and properties, such as aria-label, aria-describedby, aria-live, and aria-hidden, are allowed on any element regardless of its role. When one of them is present, the browser cannot silence the element, because that attribute needs a role to attach to. The ARIA specification resolves this conflict by ignoring the presentation role and exposing the element's implicit role instead.
The result is that role="presentation" does nothing here, which is almost always a mistake. Decide which of the two you actually want.
If the element should stay presentational, remove the global ARIA attribute. If you need the ARIA attribute, remove role="presentation" and let the element keep its semantics.
Invalid example
The aria-label cancels the presentation role, so the <div> is not silenced:
<div role="presentation" aria-label="Main navigation">
...
</div>
Valid example
Keep the label and drop the ineffective role:
<div aria-label="Main navigation">
...
</div>
Or, if the element was only meant to be presentational, remove the ARIA attribute:
<div role="presentation">
...
</div>
The CSS perspective property does not accept 0 as a valid value. It must be either none or a positive length greater than zero.
The perspective property defines the distance between the viewer and the z=0 plane, which determines the intensity of 3D effects on child elements. A value of 0 is mathematically undefined (it would place the viewer directly on the plane), so the specification rejects it. If the goal is to remove perspective entirely, use none. If the goal is a very strong perspective effect, use a small positive value like 1px.
The property applies to the element's children, not the element itself. A smaller value creates a more dramatic 3D effect, while a larger value creates a subtler one.
Invalid example
<div style="perspective: 0;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
Valid examples
To disable perspective:
<div style="perspective: none;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
To apply a strong (but valid) perspective:
<div style="perspective: 1px;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
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
<video controls>
<source src="" type="video/mp4">
Your browser does not support the video tag.
</video>
Correct: valid URL in src
<video controls>
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
Incorrect: empty src on <source> in a picture element
<picture>
<source src="" type="image/webp">
<img src="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>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
Incorrect: multiple sources with one empty src
<audio controls>
<source src="song.ogg" type="audio/ogg">
<source src="" type="audio/mpeg">
</audio>
Correct: remove the source if no file is available
<audio controls>
<source src="song.ogg" type="audio/ogg">
<source src="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:
<video id="player" controls>
Your browser does not support the video tag.
</video>
<script>
const videoUrl = getVideoUrl(); // your logic here
if (videoUrl) {
const source = 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.
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