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 URL standard (defined by WHATWG) specifies a strict set of characters allowed in each part of a URL. A space character is not among them. When the validator encounters a literal space in an href value, it reports the error “Illegal character in scheme data: space is not allowed.” This applies to spaces anywhere in the URL — the path, query string, fragment, or even after the scheme (e.g., https:).
While most modern browsers are forgiving and will attempt to fix malformed URLs by encoding spaces automatically, relying on this behavior is problematic for several reasons:
- Standards compliance: Invalid URLs violate the HTML specification, and markup that depends on browser error-correction is fragile and unpredictable.
- Accessibility: Assistive technologies, such as screen readers, may not handle malformed URLs the same way browsers do. This can result in broken links for users relying on these tools.
- Interoperability: Non-browser consumers of your HTML — search engine crawlers, link checkers, email clients, RSS readers, and APIs — may not perform the same auto-correction, leading to broken links or missed content.
- Copy-paste and sharing: When users copy a malformed URL from the source, the space can cause the link to break when pasted into other applications.
How to fix it
The fix depends on where the space appears:
- In the path or fragment: Replace each space with %20. For example, /my file.html becomes /my%20file.html.
- In the query string: You can use %20 or, if the value is part of application/x-www-form-urlencoded data, + is also acceptable for spaces within query parameter values. However, %20 is universally safe.
- Programmatically: Use encodeURI() in JavaScript to encode a full URL (it preserves structural characters like /, ?, and #). Use encodeURIComponent() to encode individual query parameter values. On the server side, use your language’s equivalent URL-encoding function.
If you’re writing URLs by hand in HTML, simply find every space and replace it with %20. If URLs are generated dynamically (from a database, CMS, or user input), ensure your templating or server-side code encodes them before inserting into the markup.
Examples
Invalid — space in the path
<a href="https://example.com/docs/My Report.pdf">Download Report</a>
The literal space between “My” and “Report” triggers the validator error.
Fixed — space encoded as %20
<a href="https://example.com/docs/My%20Report.pdf">Download Report</a>
Invalid — space in a query parameter
<a href="https://example.com/search?q=hello world">Search</a>
Fixed — space encoded in the query string
<a href="https://example.com/search?q=hello%20world">Search</a>
Invalid — multiple spaces in different URL parts
<a href="https://example.com/my folder/page two.html?ref=some value#my section">Link</a>
Fixed — all spaces encoded
<a href="https://example.com/my%20folder/page%20two.html?ref=some%20value#my%20section">Link</a>
Encoding URLs with JavaScript
If you’re building URLs dynamically, use the built-in encoding functions rather than doing manual string replacement:
<script>
// encodeURI encodes a full URL but preserves :, /, ?, #, etc.
const url = encodeURI("https://example.com/docs/My Report.pdf");
// Result: "https://example.com/docs/My%20Report.pdf"
// encodeURIComponent encodes a single value (for query params)
const query = encodeURIComponent("hello world");
// Result: "hello%20world"
</script>
Note that encodeURI() is appropriate for encoding a complete URL, while encodeURIComponent() should be used for individual components like query parameter values — it encodes characters such as / and ? that have structural meaning in a URL.
The W3C HTML Validator checks that URLs provided in href attributes conform to the URL specification. Square brackets ([ and ]) are reserved characters with very specific, limited uses in URLs — they are only permitted in the host portion of a URL to denote IPv6 addresses (e.g., http://[::1]/). When they appear elsewhere, such as in the scheme data, path, or query string without being percent-encoded, the URL is considered malformed.
This commonly happens in a few scenarios:
- Mailto links where someone wraps an email address in brackets, like mailto:[user@example.com].
- Template variables that haven’t been processed, leaving literal bracket syntax (e.g., {{, [, ]) in the rendered HTML.
- Manually constructed URLs where brackets are mistakenly used as part of the path or query string instead of being percent-encoded as %5B and %5D.
Using invalid URLs can cause browsers to misinterpret the link destination, break navigation, or cause unexpected behavior. Assistive technologies such as screen readers also rely on well-formed URLs to correctly communicate link targets to users. Keeping your URLs standards-compliant ensures consistent, predictable behavior across all browsers and devices.
How to fix it
- Remove unnecessary brackets. If the brackets are not part of the actual data (e.g., decorative brackets around an email address), simply delete them.
- Percent-encode brackets when they are part of the data. If you genuinely need square brackets in a URL’s path or query string, encode [ as %5B and ] as %5D.
- Check your templating engine output. If you use a templating system, inspect the final rendered HTML in a browser to make sure template syntax has been fully replaced with valid values.
Examples
Invalid: square brackets in a mailto URL
The brackets around the email address are not valid URL characters in this context.
<a href="mailto:[user@example.com]">Email Us</a>
Fixed: remove the brackets
<a href="mailto:user@example.com">Email Us</a>
Invalid: square brackets in a query string
<a href="https://example.com/search?filter[status]=active">Search</a>
Fixed: percent-encode the brackets
<a href="https://example.com/search?filter%5Bstatus%5D=active">Search</a>
Invalid: unprocessed template syntax in rendered HTML
If your templating engine fails to replace a variable, the final HTML may contain bracket characters:
<a href="mailto:[% user.email %]">Email Us</a>
Fixed: ensure the template renders a valid URL
Make sure the template variable resolves correctly. The rendered output should look like:
<a href="mailto:user@example.com">Email Us</a>
In your template source, this might be written as:
<a href="mailto:{{ user.email }}">Email Us</a>
The key is that the final HTML delivered to the browser must contain a valid, bracket-free URL (unless the brackets are properly percent-encoded). Always validate your rendered output, not just your template source, to catch issues like this.
A URL is made up of several parts: scheme, host (domain), path, query, and fragment. While some of these parts allow certain special characters (often percent-encoded), the host portion has strict rules. Domain names follow the DNS naming conventions, which only permit ASCII letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) as label separators. Spaces are categorically forbidden.
This validation error typically occurs in two scenarios:
- A literal space appears in the domain, e.g., http://my domain.com. This is often a typo or a copy-paste error.
- A percent-encoded space (%20) appears in the domain, e.g., http://my%20domain.com. While %20 is valid in URL paths and query strings, it is not valid in the host portion. Percent-encoding does not make a space legal in a domain name — it still resolves to a space character, which DNS cannot handle.
Why this is a problem
- Broken links: Browsers cannot resolve a domain with spaces to an actual server. Users clicking the link will get an error or be taken nowhere.
- Accessibility: Screen readers and assistive technologies may announce the link, but users will encounter a dead end, creating a frustrating experience.
- Standards compliance: The WHATWG URL Standard explicitly forbids spaces in the host component. The W3C validator flags this to help you catch what is almost certainly a mistake.
- SEO impact: Search engine crawlers will treat the URL as invalid and will not follow or index it.
How to fix it
- Check for typos: The most common fix is to correct the domain to the actual, valid domain name you intended.
- Replace spaces with hyphens: If the intended domain genuinely has a word separator, the standard convention is to use hyphens (e.g., my-domain.com).
- Remove spaces entirely: Sometimes spaces are accidentally introduced and simply need to be removed (e.g., mydomain.com).
- Check the path vs. host: If the space belongs in a file path or query parameter rather than the domain, make sure it’s in the correct part of the URL and properly percent-encoded there.
Examples
❌ Literal space in the domain
<a href="http://my domain.com/page">Visit site</a>
❌ Percent-encoded space in the domain
<a href="http://my%20domain.com/page">Visit site</a>
✅ Fixed: use a hyphen in the domain
<a href="http://my-domain.com/page">Visit site</a>
✅ Fixed: remove the space entirely
<a href="http://mydomain.com/page">Visit site</a>
✅ Spaces are fine in the path (percent-encoded)
Note that %20 is valid in the path portion of a URL — just not in the domain:
<a href="http://mydomain.com/my%20page">Visit page</a>
Common mistake: space before or after the domain
Sometimes the space is hard to spot because it’s at the beginning or end of the URL, or between the scheme and domain:
<!-- ❌ Trailing space in domain -->
<a href="http://mydomain.com /page">Visit site</a>
<!-- ✅ Fixed -->
<a href="http://mydomain.com/page">Visit site</a>
If your URLs are generated dynamically (e.g., from a CMS or database), make sure to trim whitespace from the domain portion before constructing the full URL. A quick way to catch these issues during development is to validate your HTML regularly with the W3C Markup Validation Service.
URLs must conform to the URL Living Standard, which forbids tab characters (U+0009) within the host/domain portion of a URL. While some browsers may silently strip tabs and still navigate to the intended destination, this behavior is not guaranteed and should not be relied upon.
This issue typically arises from one of the following scenarios:
- Copy-paste errors: Copying a URL from a document, email, or spreadsheet that inadvertently includes tab characters.
- Template or build tool formatting: A templating engine or code generator inserting whitespace (including tabs) into a URL string, especially when the URL is constructed across multiple lines.
- Manual typos: Accidentally pressing the Tab key while editing an href value, particularly in editors that don’t visualize whitespace.
Tab characters are invisible in most code editors by default, which makes this error frustrating to track down. Enabling “show whitespace” or “show invisible characters” in your editor can help you spot the offending character.
Why this matters
- Standards compliance: The HTML specification requires that href values contain valid URLs. A tab in the domain makes the URL syntactically invalid.
- Accessibility: Screen readers and assistive technologies parse href values to announce link destinations. An invalid URL can lead to confusing or broken announcements.
- Reliability: While major browsers tend to be forgiving and strip tabs before resolving URLs, some HTTP clients, crawlers, or older browsers may not. This can cause broken links in unexpected contexts like RSS readers, email clients, or web scrapers.
How to fix it
- Enable visible whitespace in your editor to locate tab characters.
- Search for tab characters in your href values. In many editors, you can use a regex search for \t within attribute values.
- Remove the tab characters so the URL is a clean, continuous string with no embedded whitespace.
- If URLs are dynamically generated, inspect the code that builds them to ensure no tabs or other whitespace are concatenated into the domain.
Examples
❌ Incorrect: Tab character in the domain
In the example below, a tab character is embedded within the domain name (represented here as 	 for visibility, though in source code it would be an actual invisible tab):
<!-- The tab between "example" and ".com" causes the error -->
<a href="https://example .com/page">Visit Example</a>
Note: The tab character between example and .com is invisible in most editors but triggers the validation error.
✅ Correct: Clean URL with no whitespace
<a href="https://example.com/page">Visit Example</a>
❌ Incorrect: Tab introduced by multi-line URL construction in a template
This can happen when a URL is broken across lines in a template and tabs are used for indentation:
<a href="https://
example.com/page">Visit Example</a>
✅ Correct: URL on a single line with no embedded whitespace
<a href="https://example.com/page">Visit Example</a>
Tip: Finding hidden tabs
If you’re having trouble locating the tab character, try pasting your href value into a tool that reveals character codes, or run a quick check in your browser’s developer console:
// Check for tabs in a URL string
const url = document.querySelector('a').getAttribute('href');
console.log(url.includes('\t')); // true if a tab is present
console.log(JSON.stringify(url)); // shows \t explicitly in the output
Percent-encoding (also called URL encoding) is the mechanism defined by RFC 3986 for representing special or reserved characters in URLs. It works by replacing a character with a % sign followed by two hexadecimal digits that correspond to the character’s byte value. For example, a space becomes %20, an ampersand becomes %26, and a literal percent sign becomes %25. When a browser or parser encounters % in a URL, it expects the next two characters to be valid hexadecimal digits so it can decode them back to the original character.
If the parser finds a % that is not followed by two hexadecimal digits, it cannot decode the sequence, and the URL is considered invalid. The W3C validator flags this as a bad value for the href attribute.
Common causes
- A literal % that wasn’t encoded. This is the most frequent cause. For instance, a URL containing a percentage value like 50% in a query string — the % must be written as %25.
- Truncated percent-encoded sequences. A sequence like %2 is incomplete because it only has one hex digit instead of two.
- Invalid hex characters after %. A sequence like %GZ is invalid because G and Z are not hexadecimal digits (valid hex digits are 0–9 and A–F).
- Copy-pasting URLs from non-web sources. URLs pasted from documents, emails, or spreadsheets may contain unencoded special characters.
Why this matters
- Broken links. Browsers may interpret the malformed URL differently than intended, leading users to the wrong page or a 404 error.
- Standards compliance. The WHATWG URL Standard and the HTML specification require that href values contain valid URLs. Malformed URLs violate these standards.
- Accessibility. Screen readers and assistive technologies rely on well-formed URLs to provide accurate navigation information to users.
- Interoperability. While some browsers attempt error recovery on malformed URLs, behavior is not guaranteed to be consistent across all browsers and tools.
How to fix it
- Encode literal % as %25. If the % is meant to appear as part of the URL’s content (e.g., in a query parameter value), replace it with %25.
- Complete any truncated sequences. If you intended a percent-encoded character like %20, make sure both hex digits are present.
- Use proper URL encoding functions. In JavaScript, use encodeURIComponent() for query parameter values. In server-side languages, use the equivalent encoding function (e.g., urllib.parse.quote() in Python, urlencode() in PHP).
Examples
Literal % not encoded
<!-- ❌ Bad: the % in "48%" is not followed by two hex digits -->
<a href="https://example.com?width=48%">48% width</a>
<!-- ✅ Good: the % is encoded as %25 -->
<a href="https://example.com?width=48%25">48% width</a>
Truncated percent-encoded sequence
<!-- ❌ Bad: %2 is incomplete — it needs two hex digits -->
<a href="https://example.com/path%2file">Download</a>
<!-- ✅ Good: %2F is a complete encoding for "/" -->
<a href="https://example.com/path%2Ffile">Download</a>
Invalid hexadecimal characters
<!-- ❌ Bad: %XY contains non-hexadecimal characters -->
<a href="https://example.com/search?q=%XYdata">Search</a>
<!-- ✅ Good: if the intent was a literal "%XY", encode the % -->
<a href="https://example.com/search?q=%25XYdata">Search</a>
Multiple unencoded % in a URL
<!-- ❌ Bad: both % signs are unencoded -->
<a href="https://example.com?min=10%&max=90%">Range</a>
<!-- ✅ Good: both % signs are properly encoded -->
<a href="https://example.com?min=10%25&max=90%25">Range</a>
In the structure of a URL, the @ symbol has a special meaning: it separates the userinfo component (username and password) from the host. A URL with credentials follows this pattern:
scheme://username:password@hostname/path
When the username or password itself contains an @ character — for example, an email address used as a username — the browser or URL parser may not be able to determine where the credentials end and the hostname begins. For instance, in http://user@name:pass@example.com, it’s unclear whether the host is name or example.com.
The URL Standard (maintained by WHATWG) requires that any @ appearing within the userinfo component be percent-encoded as %40. Percent-encoding replaces the literal character with a % followed by its hexadecimal ASCII code (40 for @). This removes the ambiguity and ensures all parsers interpret the URL identically.
While modern browsers may attempt to handle ambiguous URLs, the behavior is not guaranteed to be consistent across all user agents, link checkers, or HTTP clients. Properly encoding these characters ensures reliable behavior everywhere and keeps your HTML valid.
Note: Including credentials directly in URLs is generally discouraged for security reasons, as they may be exposed in browser history, server logs, and referrer headers. Consider alternative authentication methods when possible.
Examples
❌ Incorrect: unencoded @ in the username
<a href="http://user@name:password@example.com/path">Login</a>
Here, the parser cannot reliably distinguish user@name as the username from the @ that separates credentials from the host.
✅ Correct: percent-encoded @ in the username
<a href="http://user%40name:password@example.com/path">Login</a>
The @ within the username is encoded as %40, leaving only one literal @ to serve as the delimiter before the hostname.
❌ Incorrect: unencoded @ in the password
<a href="http://admin:p@ss@example.com/dashboard">Dashboard</a>
✅ Correct: percent-encoded @ in the password
<a href="http://admin:p%40ss@example.com/dashboard">Dashboard</a>
❌ Incorrect: email address used as username without encoding
<a href="ftp://joe@example.org:secret@ftp.example.com/files">Files</a>
✅ Correct: email address with @ percent-encoded
<a href="ftp://joe%40example.org:secret@ftp.example.com/files">Files</a>
To fix this issue, identify every @ character that appears before the final @ in the authority section of the URL and replace it with %40. The last @ in the authority is the actual delimiter and must remain as a literal character.
The <area> element defines a clickable region within an <map> element, which is used with images to create image maps. When an <area> element includes an href attribute, the browser treats it as a hyperlink. The value of href must be a valid URL, and http:// alone fails validation because the URL specification requires a host after the :// separator. An empty host is not permitted.
This matters for several reasons. Browsers may handle malformed URLs unpredictably — some might ignore the link, others might attempt navigation to a nonsensical destination, and others might throw a network error. Screen readers and other assistive technologies rely on valid href values to announce links correctly and provide meaningful navigation to users. From a standards compliance perspective, the WHATWG URL Standard explicitly rejects URLs with empty hosts, and the W3C validator flags this as an error.
This issue commonly appears when placeholder URLs are left in during development, when CMS tools generate incomplete markup, or when a URL value is dynamically constructed but the host portion ends up empty.
How to fix it
- If the area should link somewhere, replace http:// with the full, intended URL including the host (e.g., https://example.com/page).
- If the area is a temporary placeholder, use href="#" to create a valid no-op link, though be aware this will scroll the page to the top when clicked.
- If the area shouldn’t be interactive, remove the href attribute entirely. Without href, the <area> element is not a hyperlink and won’t be clickable.
Examples
Invalid: empty host in URL
The value http:// has no host, which triggers the validation error.
<img src="diagram.png" alt="Site map" usemap="#siteMap">
<map name="siteMap">
<area shape="rect" coords="30,23,183,191" href="http://" alt="Home page">
</map>
Fixed: complete URL with a valid host
Providing a full URL with a host resolves the error.
<img src="diagram.png" alt="Site map" usemap="#siteMap">
<map name="siteMap">
<area shape="rect" coords="30,23,183,191" href="https://example.com/" alt="Home page">
</map>
Fixed: placeholder link with #
If you need the area to remain clickable but don’t have a destination yet, href="#" is a valid placeholder.
<img src="diagram.png" alt="Site map" usemap="#siteMap">
<map name="siteMap">
<area shape="rect" coords="30,23,183,191" href="#" alt="Home page">
</map>
Fixed: non-interactive area without href
If the area doesn’t need to be a link, simply omit the href attribute.
<img src="diagram.png" alt="Site map" usemap="#siteMap">
<map name="siteMap">
<area shape="rect" coords="30,23,183,191" alt="Home page">
</map>
URLs used in HTML attributes must follow the URL Living Standard, which defines a specific set of characters that are allowed in each part of a URL. The query component of a URL — everything after the ? — permits most printable ASCII characters, but certain characters are still considered illegal and must be percent-encoded. When the W3C validator encounters one of these forbidden characters in the href of a <link> element, it raises this error.
Common characters that trigger this issue include:
| Character | Percent-encoded |
|---|
| | (pipe) | %7C | | [ (left bracket) | %5B | | ] (right bracket) | %5D | | { (left brace) | %7B | | } (right brace) | %7D | | ^ (caret) | %5E | | ` (backtick) | %60 | | (space) | %20 |
Why this matters
While many modern browsers are lenient and will silently fix malformed URLs, relying on this behavior is risky. Invalid URLs can cause problems in several ways:
- Inconsistent browser behavior: Not all user agents handle illegal characters the same way, which can lead to broken stylesheets or resources failing to load.
- Interoperability issues: Proxies, CDNs, and other intermediaries may reject or mangle URLs with illegal characters.
- Standards compliance: Valid HTML requires valid URLs in attributes. An illegal character in the href makes the entire document non-conforming.
- Copy-paste and sharing reliability: Malformed URLs are more likely to break when shared across systems, emails, or documentation.
How to fix it
Identify the illegal characters in your URL’s query string and replace each one with its percent-encoded equivalent. If you’re generating URLs programmatically, use a proper URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python) to ensure all special characters are encoded correctly.
Examples
❌ Illegal pipe character in the query string
This is a common pattern seen with Google Fonts URLs that use | to separate font families:
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto">
✅ Pipe character percent-encoded
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans%7CRoboto">
❌ Square brackets in the query string
Some APIs or frameworks use bracket notation in query parameters:
<link rel="stylesheet" href="https://example.com/styles?themes[]=dark&themes[]=compact">
✅ Square brackets percent-encoded
<link rel="stylesheet" href="https://example.com/styles?themes%5B%5D=dark&themes%5B%5D=compact">
❌ Space character in the query string
<link rel="stylesheet" href="https://example.com/css?file=my styles.css">
✅ Space character percent-encoded
<link rel="stylesheet" href="https://example.com/css?file=my%20styles.css">
Note that for Google Fonts specifically, the modern API (v2) uses a different URL format that avoids the pipe character altogether. Where possible, consider updating to the latest version of an API rather than just encoding the old URL.
The <pattern> element lives inside <svg>, and SVG is an XML-based language. Unlike regular HTML — where id values follow relatively relaxed rules — SVG content must comply with XML 1.0 naming conventions. This means id values have stricter character and formatting requirements than you might be used to in plain HTML.
XML 1.0 Name Rules
An XML 1.0 name (used for id attributes in SVG) must follow these rules:
- First character must be a letter (A–Z, a–z) or an underscore (_).
- Subsequent characters can be letters, digits (0–9), hyphens (-), underscores (_), or periods (.).
- Spaces and special characters like !, @, #, $, %, (, ), etc. are not allowed anywhere in the name.
Common mistakes that trigger this error include starting an id with a digit (e.g., 1pattern), a hyphen (e.g., -myPattern), or a period (e.g., .dotPattern), or including characters like spaces or colons.
Why This Matters
- Standards compliance: SVG is parsed as XML in many contexts. An invalid XML name can cause parsing errors or unexpected behavior, especially when SVG is served with an XML MIME type or embedded in XHTML.
- Functionality: The <pattern> element’s id is typically referenced via url(#id) in fill or stroke attributes. An invalid id may cause the pattern reference to silently fail, leaving elements unfilled or invisible.
- Cross-browser consistency: While some browsers are lenient with invalid XML names, others are not. Using valid names ensures consistent rendering across all browsers and environments.
How to Fix
Rename the id value so it starts with a letter or underscore and contains only valid characters. If you reference this id elsewhere (e.g., in fill="url(#...)" or in CSS), update those references to match.
Examples
❌ Invalid: id starts with a digit
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="1stPattern" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="blue" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#1stPattern)" />
</svg>
❌ Invalid: id starts with a hyphen
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="-stripe-bg" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="5" height="10" fill="red" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#-stripe-bg)" />
</svg>
❌ Invalid: id contains special characters
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="my pattern!" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="green" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#my pattern!)" />
</svg>
✅ Valid: id starts with a letter
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="firstPattern" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="blue" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#firstPattern)" />
</svg>
✅ Valid: id starts with an underscore
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="_stripe-bg" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="5" height="10" fill="red" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#_stripe-bg)" />
</svg>
✅ Valid: Using letters, digits, hyphens, and underscores
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dot-grid_v2" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="green" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#dot-grid_v2)" />
</svg>
Note that this same XML 1.0 naming rule applies to id attributes on all SVG elements — not just <pattern>. If you see similar errors on elements like <linearGradient>, <clipPath>, or <filter>, the same fix applies: ensure the id starts with a letter or underscore and uses only valid characters.
The integrity attribute enables Subresource Integrity (SRI), a security feature that lets browsers verify that a fetched resource (such as a JavaScript file from a CDN) has not been tampered with. The attribute value follows the format [algorithm]-[base64-encoded hash], where the algorithm is typically sha256, sha384, or sha512, and the hash is a base64 representation of the file’s cryptographic digest.
Base64 encoding works by converting binary data into a string of ASCII characters drawn from a 64-character alphabet. A key property of valid base64 is that the encoded output must always have a length that is a multiple of 4. When the raw binary data doesn’t divide evenly, the output is padded with one or two = characters to reach the correct length. If the base64 string in your integrity attribute has an incorrect length — for instance, it was truncated, manually edited, or copied incompletely — the validator will flag it as invalid.
This matters for several reasons:
- Security: If the integrity value is malformed, the browser cannot verify the resource. Depending on the browser, it may block the script entirely, breaking your site’s functionality.
- Standards compliance: The HTML specification requires the hash portion to be a valid base64 string. An invalid value is a conformance error.
- Reliability: A malformed hash will never match any file, so the SRI check will always fail, effectively making the script unusable.
Common causes of this error include:
- Copying the hash value incompletely (missing trailing = padding or other characters).
- Manually modifying the hash string.
- Using a tool that produced an incorrectly encoded output.
- Mixing up base64 and base64url encodings (base64url uses - and _ instead of + and /, and often omits padding).
To fix the issue, regenerate the correct SRI hash for the exact file being referenced. You can do this with the command line:
openssl dgst -sha384 -binary script.js | openssl base64 -A
Or using shasum and base64:
shasum -b -a 384 script.js | awk '{print $1}' | xxd -r -p | base64
Online tools like the SRI Hash Generator can also produce the correct value. After generating the hash, prepend the algorithm prefix (e.g., sha384-) and verify that the base64 portion has a length divisible by 4.
Examples
Incorrect: Malformed base64 value
The hash below is not a valid base64 string — its length is not a multiple of 4, and it contains the character !, which is not in the base64 alphabet.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-BadBase64Value!"
crossorigin="anonymous"></script>
Incorrect: Truncated hash missing padding
This hash has been accidentally truncated, losing the trailing = padding characters that make it a valid base64 string.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8t"
crossorigin="anonymous"></script>
Correct: Properly formatted SRI hash
The base64 hash is the correct length (a multiple of 4) and uses only valid base64 characters. The trailing T completes the final 4-character group without needing padding in this case, but other hashes may end with = or ==.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8tT"
crossorigin="anonymous"></script>
Correct: Multiple hashes for fallback
You can provide multiple hash values separated by spaces. Each one must be independently valid base64 with the correct length.
<script
src="https://cdn.example.com/library.js"
integrity="sha256-BpfGp+xz0kN9mMKoECQbGRab7fMi/dsRZ7Sy7LVd3CE= sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8tT"
crossorigin="anonymous"></script>
Always ensure the hash is generated from the exact file the src attribute points to. If the CDN serves a different version or a minified variant, the hash will not match, and the browser will block the resource regardless of whether the base64 encoding is valid.
The lang attribute on the <html> element tells browsers, screen readers, and search engines what language the page content is written in. Its value must follow the BCP 47 standard, which uses ISO 639 language codes as the primary language subtag. When the validator reports that a language subtag “is not a valid ISO language part of a language tag,” it means the code you provided doesn’t match any recognized language in the ISO 639 registry.
Common Causes
Typos in the language code
A simple misspelling like emg instead of en, or fre instead of fr, will trigger this error.
Using a country code instead of a language code
Country codes (ISO 3166) and language codes (ISO 639) are different standards. For example, uk is the country code for the United Kingdom, but it’s also the valid language code for Ukrainian. Using gb (Great Britain) as a language would be invalid. Similarly, us is not a language code — you need en for English or en-US for American English specifically.
Using made-up or deprecated codes
Codes like xx, en-UK, or other non-standard values will fail validation. Note that while en-US and en-GB are valid (language-region format), en-UK is not because UK is not the correct ISO 3166-1 region subtag for the United Kingdom — GB is.
Why This Matters
- Accessibility: Screen readers rely on the lang attribute to select the correct pronunciation rules and voice profile. An invalid language code can cause assistive technology to mispronounce content or fall back to a default language.
- SEO: Search engines use the lang attribute as a signal for serving the right content to users in the appropriate language and region.
- Browser behavior: Browsers use the language tag for spell-checking, hyphenation, font selection, and other language-sensitive rendering decisions.
How to Fix It
- Identify the language your page is written in.
- Look up the correct ISO 639-1 two-letter code (preferred) or ISO 639-2 three-letter code for that language.
- If you need to specify a regional variant, append a hyphen and the ISO 3166-1 region code (e.g., pt-BR for Brazilian Portuguese).
- Replace the invalid value in the lang attribute.
Some commonly used valid language codes:
| Language | Code |
|---|---|
| English | en |
| English (US) | en-US |
| English (UK) | en-GB |
| Spanish | es |
| French | fr |
| German | de |
| Portuguese (Brazil) | pt-BR |
| Chinese (Simplified) | zh-Hans |
| Japanese | ja |
| Arabic | ar |
Examples
❌ Invalid: Typo in language code
<html lang="emg">
❌ Invalid: Country code used instead of language code
<html lang="us">
❌ Invalid: Incorrect region subtag
<html lang="en-UK">
❌ Invalid: Made-up language code
<html lang="english">
✅ Valid: Correct two-letter language code
<html lang="en">
✅ Valid: Language with region subtag
<html lang="en-GB">
✅ Valid: Full document with proper lang attribute
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Ma page</title>
</head>
<body>
<p>Bonjour le monde !</p>
</body>
</html>
You can verify your language tag using the IANA Language Subtag Registry or the BCP 47 Language Subtag Lookup tool to ensure your code is valid before updating your markup.
The loading attribute tells the browser how to prioritize loading an image. Setting it to "lazy" defers loading until the image is near the viewport, which can improve page performance by reducing initial load time and saving bandwidth. Setting it to "eager" instructs the browser to load the image immediately, regardless of its position on the page. When the attribute is omitted, the browser defaults to "eager" behavior.
Any value other than these two exact lowercase strings is invalid according to the HTML specification. Common mistakes include using numeric values like "1" or "0", boolean-style values like "true" or "false", uppercase variants like "Lazy", or misspellings like "laxy". When a browser encounters an invalid value, it ignores the attribute and falls back to the default eager loading behavior. While this means the page won’t break visually, the intended lazy-loading optimization won’t take effect, and the HTML will fail W3C validation.
This matters for a few reasons:
- Performance: If you intended to use "lazy" but provided an invalid value, your images will all load eagerly, defeating the purpose and potentially degrading page speed.
- Standards compliance: Invalid attribute values signal potential bugs in your markup and can cause issues with tools and parsers that expect well-formed HTML.
- Maintainability: Valid, predictable markup is easier to maintain and debug across teams and over time.
To fix the issue, check the loading attribute value and replace it with one of the two valid options. If you want deferred loading, use "lazy". If you want immediate loading (or don’t need to specify), use "eager" or simply remove the attribute.
Examples
Invalid values
Each of these will trigger the validation error:
<!-- Numeric value -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="1">
<!-- Boolean-style value -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="true">
<!-- Misspelled value -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="laxy">
<!-- Wrong case -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="Lazy">
Correct: lazy loading
Use "lazy" for images that are below the fold or not immediately visible to the user:
<img src="photo.jpg" alt="A sunset over the ocean" loading="lazy">
Correct: eager loading
Use "eager" for critical images that should load right away, such as hero images or logos:
<img src="logo.png" alt="Company logo" loading="eager">
Correct: omitting the attribute
Since "eager" is the default behavior, you can simply remove the attribute if you want immediate loading:
<img src="logo.png" alt="Company logo">
Practical usage in context
A common and effective pattern is to eagerly load above-the-fold images while lazy-loading everything else:
<!-- Hero image: load immediately -->
<img src="hero-banner.jpg" alt="Welcome to our store" loading="eager">
<!-- Product images further down the page: defer loading -->
<img src="product-1.jpg" alt="Running shoes" loading="lazy">
<img src="product-2.jpg" alt="Hiking boots" loading="lazy">
<img src="product-3.jpg" alt="Sandals" loading="lazy">
Note that the loading attribute also works on iframe elements with the same two valid values: "lazy" and "eager".
The media attribute tells the browser under which conditions a linked stylesheet (or other resource) should be applied. Its value must be a valid media query list as defined by the CSS Media Queries specification. When the W3C HTML Validator reports a parse error for this attribute, it means the value couldn’t be parsed as a valid media query. The browser may ignore the attribute entirely or behave unpredictably, potentially loading the stylesheet in all conditions or not at all.
Common causes of this error include:
- Typos in media types or features — e.g., scrren instead of screen, or max-widht instead of max-width.
- Missing parentheses around media features — e.g., max-width: 600px instead of (max-width: 600px).
- Invalid or stray characters — e.g., extra commas, semicolons, or unmatched parentheses.
- Using CSS syntax instead of media query syntax — e.g., writing @media screen or including curly braces.
- Incorrect logical operators — e.g., using or at the top level instead of a comma, or misspelling not or only.
- Server-side template placeholders left unresolved — e.g., media="{{mediaQuery}}" rendered literally.
Getting this right matters for standards compliance and predictable cross-browser behavior. Browsers are generally lenient and may try to recover from malformed media queries, but the recovery behavior isn’t guaranteed to be consistent. A broken media attribute can cause stylesheets to load when they shouldn’t (wasting bandwidth) or not load when they should (breaking layout). It also affects performance, since browsers use the media attribute to prioritize resource loading.
To fix the issue, verify that your media value is a syntactically correct media query. Use valid media types (screen, print, all), wrap media features in parentheses, and separate multiple queries with commas.
Examples
Incorrect: missing parentheses around media feature
<!-- ❌ Parse error: media features must be wrapped in parentheses -->
<link rel="stylesheet" href="responsive.css" media="max-width: 600px">
Correct: parentheses around media feature
<link rel="stylesheet" href="responsive.css" media="(max-width: 600px)">
Incorrect: typo in media type
<!-- ❌ Parse error: "scrren" is not a valid media type -->
<link rel="stylesheet" href="styles.css" media="scrren">
Correct: valid media type
<link rel="stylesheet" href="styles.css" media="screen">
Incorrect: using or instead of a comma to separate queries
<!-- ❌ Parse error: top-level "or" is not valid between media queries -->
<link rel="stylesheet" href="styles.css" media="screen or print">
Correct: comma-separated media query list
<link rel="stylesheet" href="styles.css" media="screen, print">
Incorrect: stray semicolon in the value
<!-- ❌ Parse error: semicolons are not valid in media queries -->
<link rel="stylesheet" href="styles.css" media="screen; (max-width: 768px)">
Correct: combining media type and feature with and
<link rel="stylesheet" href="styles.css" media="screen and (max-width: 768px)">
More valid media query examples
<!-- Simple media type -->
<link rel="stylesheet" href="print.css" media="print">
<!-- Apply to all media (default behavior, but explicit) -->
<link rel="stylesheet" href="base.css" media="all">
<!-- Multiple conditions with "and" -->
<link rel="stylesheet" href="tablet.css" media="screen and (min-width: 768px) and (max-width: 1024px)">
<!-- Using "not" to exclude a media type -->
<link rel="stylesheet" href="no-print.css" media="not print">
<!-- Multiple queries separated by commas (logical OR) -->
<link rel="stylesheet" href="special.css" media="(max-width: 600px), (orientation: portrait)">
<!-- Prefers-color-scheme for dark mode -->
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
If you’re unsure whether your media query is valid, try testing it in a browser’s developer tools by adding a <style> block with @media and the same query — if the browser highlights it as invalid, the same value will fail in the media attribute.
The media attribute on a <link> element specifies which media or device types the linked resource (typically a stylesheet) is designed for. It accepts a valid media query or a comma-separated list of media types. In the current CSS specification (Media Queries Level 4), only three media types remain valid: all, screen, and print. All other media types from older specifications — including projection, handheld, tv, tty, braille, embossed, and aural — have been deprecated.
The projection media type was originally intended to target presentation and projector-based displays. In practice, browser support for it was extremely limited (only Opera had meaningful support), and the use case never gained traction. The CSS Working Group deprecated it because the distinction between a “screen” and a “projection” display is no longer meaningful — modern browsers treat projectors, external displays, and monitors uniformly under the screen type.
Why this matters
- Standards compliance: Using deprecated media types causes W3C validation errors, which can signal broader code quality issues.
- No practical effect: Modern browsers simply ignore unrecognized media types. If projection is the only value in your media attribute, the stylesheet may not load at all in some browsers. If it appears alongside screen, the browser loads the stylesheet based on the screen match and silently discards projection — meaning the deprecated value adds nothing.
- Maintainability: Keeping deprecated values in your code can confuse other developers and create the false impression that the stylesheet has special behavior for projectors.
How to fix it
- Remove projection from the media attribute value.
- If screen or another valid type was already listed alongside projection, keep the valid type.
- If projection was the only value, replace it with screen (since projectors are treated as screens by modern browsers).
- If the stylesheet should apply universally, remove the media attribute entirely or set it to all.
Examples
Incorrect
Using the deprecated projection media type alongside screen:
<link rel="stylesheet" href="style.css" media="screen, projection">
Using projection as the sole media type:
<link rel="stylesheet" href="slides.css" media="projection">
Correct
Remove projection and keep the valid screen type:
<link rel="stylesheet" href="style.css" media="screen">
Replace projection with screen, since projectors are handled as screens:
<link rel="stylesheet" href="slides.css" media="screen">
Target both screens and print:
<link rel="stylesheet" href="style.css" media="screen, print">
If the stylesheet should apply to all devices, omit the media attribute (which defaults to all):
<link rel="stylesheet" href="style.css">
Or explicitly set it to all:
<link rel="stylesheet" href="style.css" media="all">
Using media queries instead of media types
If you need more granular control over when a stylesheet applies — for example, targeting large displays commonly used for presentations — you can use a media query with feature conditions instead of relying on deprecated media types:
<link rel="stylesheet" href="presentation.css" media="screen and (min-width: 1920px)">
This approach is standards-compliant and gives you far more precise targeting than the old media types ever provided.
The media attribute on a <link> element specifies the conditions under which the linked resource should apply. It accepts either a simple media type or a full media query. When the validator reports “unrecognized media,” it means the value you provided doesn’t match any known media type or valid media query syntax.
Several older media types that were defined in earlier CSS specifications have been deprecated. Types like handheld, projection, tv, tty, aural, braille, and embossed are no longer recognized as valid. Modern CSS and HTML only support three media types: all, screen, and print. If you’re using a deprecated type, you should replace it with an appropriate modern media query that targets the device characteristics you need.
Beyond deprecated types, this error also occurs when a media query expression is malformed — for example, missing parentheses around a feature expression, using an unknown feature name, or having a typo in the value.
Why this matters
- Standards compliance: Using unrecognized media types means your HTML doesn’t conform to the current HTML and CSS specifications.
- Browser behavior: Browsers may ignore the entire <link> element or apply the resource unconditionally when they encounter an unrecognized media type, leading to unexpected results.
- Performance: The media attribute helps browsers prioritize resource loading. A valid media query allows the browser to defer loading stylesheets that don’t match the current context (e.g., print stylesheets), improving page load performance.
How to fix it
- Replace deprecated media types with screen, print, or all, or use modern media queries that target specific device features.
- Check for typos in your media type or query expression.
- Validate your media query syntax — feature expressions must be wrapped in parentheses and use recognized feature names like max-width, orientation, or prefers-color-scheme.
Examples
Incorrect: using a deprecated media type
<link rel="stylesheet" href="mobile.css" media="handheld">
The handheld media type is deprecated and will trigger the validation error.
Incorrect: misspelled media type
<link rel="stylesheet" href="styles.css" media="screen">
Incorrect: malformed media query
<link rel="stylesheet" href="responsive.css" media="max-width: 768px">
The feature expression is missing its surrounding parentheses.
Correct: using valid media types
<link rel="stylesheet" href="general.css">
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="screen.css" media="screen">
When no media attribute is specified, it defaults to all.
Correct: replacing deprecated types with modern media queries
Instead of media="handheld", use a media query that targets small screens or specific device capabilities:
<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 768px)">
Correct: using complex media queries
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="portrait.css" media="screen and (orientation: portrait)">
<link rel="stylesheet" href="large.css" media="screen and (min-width: 1200px)">
Valid media types reference
| Media type | Description |
|---|---|
| all | Matches all devices (default when omitted) |
| Matches printers and print preview mode | |
| screen | Matches screens (computers, tablets, phones) |
For anything more specific than these three types, use media feature expressions like (max-width: 600px), (hover: hover), or (prefers-reduced-motion: reduce) to target the exact device characteristics you need.
The multiple attribute tells the browser that the user can supply more than one value for a given input. For <input type="file">, it allows selecting multiple files at once. For <input type="email">, it allows entering a comma-separated list of email addresses. These are the only two input types that support the multiple attribute according to the HTML specification.
This validation error can appear for two distinct reasons, and sometimes both at once:
-
An invalid value is assigned to the boolean attribute. Boolean attributes in HTML follow strict rules. Valid syntaxes are: the attribute name alone (multiple), an empty string value (multiple=""), or the attribute’s own name as the value (multiple="multiple"). Any other value — including multiple="true", multiple="1", or multiple="yes" — is invalid.
-
The attribute is used on an unsupported input type. Placing multiple on input types like text, number, password, or url is not valid because those types don’t define behavior for this attribute. Browsers will simply ignore it, but it still constitutes invalid markup.
Why this matters
- Standards compliance: Invalid boolean attribute values violate the WHATWG HTML specification. While most browsers are forgiving and may still interpret multiple="true" as the attribute being present, relying on this behavior is fragile and non-standard.
- Accessibility: Assistive technologies rely on valid, well-structured markup. An invalid attribute value could lead to unpredictable behavior in screen readers or other tools.
- Maintainability: Using multiple on an unsupported input type suggests a misunderstanding of the element’s capabilities, which can confuse other developers and lead to bugs.
How to fix it
- Remove the value entirely: Change multiple="1" or multiple="true" to just multiple.
- Use a valid boolean syntax if a value is required: Some templating systems or XML-based contexts (like XHTML) require explicit attribute values. In those cases, use multiple="" or multiple="multiple".
- Ensure the input type supports multiple: Only type="email" and type="file" accept this attribute. If you need multi-value input for other types, consider alternative approaches like multiple separate inputs, a <select multiple> element, or a JavaScript-based solution.
Examples
Invalid: wrong value on a boolean attribute
<!-- Bad: "1" is not a valid boolean attribute value -->
<input type="file" name="attachments" multiple="1">
<!-- Bad: "true" is not a valid boolean attribute value -->
<input type="email" name="recipients" multiple="true">
Invalid: multiple on an unsupported input type
<!-- Bad: type="text" does not support the multiple attribute -->
<input type="text" name="tags" multiple>
<!-- Bad: type="number" does not support the multiple attribute -->
<input type="number" name="quantities" multiple>
Valid: correct usage of multiple
<!-- Correct: boolean attribute with no value -->
<input type="file" name="attachments" multiple>
<!-- Correct: empty string value (valid boolean syntax) -->
<input type="email" name="recipients" multiple="">
<!-- Correct: attribute name as value (valid for XHTML compatibility) -->
<input type="file" name="documents" multiple="multiple">
Full corrected document
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Form</title>
</head>
<body>
<form action="/submit" method="post" enctype="multipart/form-data">
<label for="recipients">Recipients:</label>
<input type="email" id="recipients" name="recipients" multiple placeholder="a@example.com, b@example.com">
<label for="files">Attachments:</label>
<input type="file" id="files" name="files" multiple>
<button type="submit">Send</button>
</form>
</body>
</html>
In HTML, the name attribute on an <iframe> defines a browsing context name. This name can be referenced by other elements — for example, a link with target="my-frame" will open its URL inside the <iframe> whose name is "my-frame". The HTML specification reserves all browsing context names that start with an underscore for special keywords:
- _self — the current browsing context
- _blank — a new browsing context
- _parent — the parent browsing context
- _top — the topmost browsing context
Because the underscore prefix is reserved for these (and potentially future) keywords, the spec requires that any custom browsing context name must not begin with _. Setting name="_example" or name="_myFrame" on an <iframe> is invalid HTML, even though those exact strings aren’t currently defined keywords. Browsers may handle these inconsistently — some might ignore the name entirely, while others could treat it as one of the reserved keywords, leading to unexpected navigation behavior.
This matters for several reasons:
- Standards compliance: The WHATWG HTML living standard explicitly states that a valid browsing context name must not start with an underscore character.
- Predictable behavior: Using a reserved prefix can cause links or forms targeting that <iframe> to navigate in unintended ways (e.g., opening in a new tab instead of within the frame).
- Future-proofing: New underscore-prefixed keywords could be added to the spec, which might break pages that use custom names starting with _.
To fix the issue, simply rename the name attribute value so it doesn’t start with an underscore. You can use underscores elsewhere in the name — just not as the first character.
Examples
❌ Invalid: name starts with an underscore
<iframe src="https://example.com" name="_example"></iframe>
<a href="https://example.com/page" target="_example">Open in frame</a>
The name _example starts with an underscore, which makes it invalid. A browser might interpret _example unpredictably or ignore the name entirely when used as a target.
✅ Fixed: underscore removed from the start
<iframe src="https://example.com" name="example"></iframe>
<a href="https://example.com/page" target="example">Open in frame</a>
✅ Fixed: underscore used elsewhere in the name
<iframe src="https://example.com" name="my_example"></iframe>
<a href="https://example.com/page" target="my_example">Open in frame</a>
Underscores are perfectly fine as long as they aren’t the first character.
❌ Invalid: using a reserved keyword as a frame name
<iframe src="https://example.com" name="_blank"></iframe>
Using _blank as an <iframe> name is also invalid because it’s a reserved browsing context keyword. A link targeting _blank would open in a new window/tab rather than inside this <iframe>, which is almost certainly not what you intended.
✅ Fixed: descriptive name without underscore prefix
<iframe src="https://example.com" name="content-frame"></iframe>
<a href="https://example.com/page" target="content-frame">Open in frame</a>
How to fix
- Find every <iframe> element whose name attribute starts with _.
- Rename each one by removing the leading underscore or replacing it with a letter or other valid character.
- Update any target attributes on <a>, <form>, or <base> elements that reference the old name so they match the new name.
- Re-validate your HTML to confirm the issue is resolved.
Understanding Boolean Attributes in HTML
In HTML, boolean attributes work differently from regular attributes. A boolean attribute’s presence on an element represents true, and its absence represents false. According to the HTML specification, a boolean attribute may only have three valid forms:
- The attribute name alone: novalidate
- The attribute with an empty value: novalidate=""
- The attribute with a value matching its own name (case-insensitive): novalidate="novalidate"
Any other value — such as "true", "false", "1", "0", or "yes" — is invalid and triggers this validation error. This is a common source of confusion, especially for developers coming from frameworks like React (which uses noValidate={true}) or from languages where boolean attributes accept explicit true/false strings.
Why This Matters
- Standards compliance: Using invalid values violates the HTML specification and will cause W3C validation failures.
- Unexpected behavior: While most browsers are lenient and treat any value of novalidate as “present” (meaning even novalidate="false" would disable validation, not enable it), relying on this behavior is unreliable and misleading to other developers reading your code.
- Maintainability: Writing novalidate="false" suggests the form should be validated, but the opposite is true — the attribute is present, so validation is skipped. This creates confusing, error-prone code.
About the novalidate Attribute
The novalidate attribute tells the browser to skip its built-in constraint validation when the form is submitted. Without it, the browser checks required fields, input patterns, email formats, and other constraints before allowing submission.
If novalidate is not set on the form, individual submit buttons can still bypass validation on a per-button basis using the formnovalidate attribute on a <button>, <input type="submit">, or <input type="image"> element.
Examples
❌ Invalid: Arbitrary value on novalidate
<form method="post" novalidate="true">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
This triggers the error because "true" is not a valid value for a boolean attribute. Other invalid variations include:
<form method="post" novalidate="1">
<form method="post" novalidate="yes">
<form method="post" novalidate="false">
Note that novalidate="false" is especially dangerous — it does not enable validation. Because the attribute is present, the browser disables validation regardless of the value.
✅ Valid: Attribute name alone (recommended)
<form method="post" novalidate>
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Valid: Empty string value
<form method="post" novalidate="">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Valid: Value matching the attribute name
<form method="post" novalidate="novalidate">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Valid: Removing the attribute to enable validation
If you want the form to be validated, simply remove the novalidate attribute entirely:
<form method="post">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Using formnovalidate on a specific button
If you want validation on the primary submit but want to skip it for a “Save Draft” button, use formnovalidate on the button instead:
<form method="post">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
<button formnovalidate>Save Draft</button>
</form>
This pattern keeps validation active for the main submission while allowing drafts to bypass it — without needing novalidate on the form at all.
The ping attribute specifies a space-separated list of URLs that the browser should notify (via a small POST request) when a user follows a hyperlink. This is commonly used for click tracking and analytics. According to the HTML specification, every URL in the list must be a valid, non-empty URL that uses either the http or https scheme — no other schemes or relative paths are permitted.
This restriction exists for practical and security reasons. The ping mechanism is specifically designed for web-based tracking endpoints, so only web protocols make sense. Relative URLs are disallowed because the ping is sent as a separate request independent of normal navigation, and the specification requires absolute URLs to unambiguously identify the target server. Using invalid values won’t produce the intended tracking behavior and will cause the browser to silently ignore the ping attribute entirely.
From an accessibility and standards compliance standpoint, ensuring valid ping values means your analytics will work reliably in browsers that support the attribute. Note that browser support varies — some browsers (notably Firefox) disable ping by default or hide it behind a preference — so you should not rely on it as your sole tracking mechanism.
How to fix it
- Replace relative URLs with absolute URLs. If you have a value like /track or track.php, prepend the full origin (e.g., https://example.com/track).
- Remove non-HTTP schemes. Values like mailto:someone@example.com or ftp://example.com/log are not valid for ping.
- Ensure each URL in the list is properly formatted. Multiple URLs must be separated by spaces (not commas or semicolons), and each one must be a complete http or https URL.
Examples
Incorrect: relative URL
<a href="https://example.com" ping="/track">Visit Example</a>
The value /track is a relative URL, which is not allowed in the ping attribute.
Incorrect: unsupported scheme
<a href="https://example.com" ping="ftp://example.com/log">Visit Example</a>
The ftp: scheme is not permitted — only http and https are valid.
Incorrect: comma-separated URLs
<a href="https://example.com" ping="https://example.com/track, https://analytics.example.com/log">Visit Example</a>
Multiple URLs must be space-separated, not comma-separated. The commas make each URL invalid.
Correct: single absolute URL
<a href="https://example.com" ping="https://example.com/track">Visit Example</a>
Correct: multiple space-separated absolute URLs
<a href="https://example.com" ping="https://example.com/track https://analytics.example.com/log">Visit Example</a>
Each URL is a fully qualified https URL, and they are separated by a single space. Both will receive a POST request when the link is clicked (in browsers that support the ping attribute).
The poster attribute specifies an image to display as a placeholder while the video is loading or before the user starts playback. Like all HTML attributes that accept URLs — such as src, href, and action — the value must conform to valid URI syntax as defined by RFC 3986. In this standard, a literal space character is not a legal character in any part of a URL. When the validator encounters a space in the poster attribute’s value, it flags it as an illegal character in the path segment.
While most modern browsers are forgiving and will attempt to resolve URLs containing raw spaces by internally encoding them, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires valid URLs. Raw spaces violate this requirement.
- Interoperability: Not all user agents, HTTP clients, or content delivery systems handle unencoded spaces the same way. Some may truncate the URL at the first space or fail to resolve the resource entirely.
- Portability: If your HTML is consumed by tools, scrapers, or APIs that strictly parse URLs, unencoded spaces can cause silent failures.
- Consistency: Keeping URLs properly encoded prevents subtle bugs when paths are constructed dynamically in server-side or client-side code.
The fix is straightforward. You have two options:
- Percent-encode the spaces: Replace every space in the URL with %20. This preserves the original file and folder names on the server while producing a valid URL in your HTML.
- Eliminate spaces from file and folder names: Use hyphens (-), underscores (_), or camelCase instead of spaces. This is generally considered best practice for web assets, as it avoids encoding issues across the board.
Note that this rule applies to the entire URL path, not just the filename. If any directory in the path contains a space, it must also be encoded or renamed. The same principle applies to other special characters that are reserved or disallowed in URLs, such as {, }, |, ^, and [.
Examples
Incorrect — space in the path
The folder name video images contains a space, which is illegal in a URL path segment.
<video controls poster="/img/video images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Incorrect — space in the filename
The filename my poster.jpg also triggers the same error.
<video controls poster="/img/my poster.jpg">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — percent-encoding the spaces
Each space is replaced with %20, producing a valid URL.
<video controls poster="/img/video%20images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — removing spaces from the path
Renaming the folder to use a hyphen eliminates the need for encoding entirely.
<video controls poster="/img/video-images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — removing spaces from the filename
<video controls poster="/img/my-poster.jpg">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
As a general best practice, avoid spaces in all file and folder names used on the web. Use hyphens or underscores instead. If you’re working with files you can’t rename — such as assets from a CMS or third-party system — always percent-encode spaces as %20 in your HTML. This applies not only to poster but to every attribute that takes a URL value, including src, href, action, data, and formaction.
The rel attribute defines the relationship between the current document and a linked resource. The HTML specification maintains a set of recognized keyword values for this attribute, and the allowed keywords vary depending on which element the attribute appears on. For example, stylesheet is valid on <link> but not on <a>, while nofollow is valid on <a> and <form> but not on <link>.
When the validator encounters a rel value that isn’t a recognized keyword, it checks whether the value is a valid absolute URL. This is because the HTML specification allows custom link types to be defined using absolute URLs as identifiers (similar to how XML namespaces work). If the value is neither a recognized keyword nor a valid absolute URL, the validator raises this error.
Common causes of this error include:
- Typos in standard keywords — for example, rel="styelsheet" or rel="no-follow" instead of the correct rel="stylesheet" or rel="nofollow".
- Using non-standard or invented values — such as rel="custom" or rel="external", which aren’t part of the HTML specification’s recognized set.
- Using relative URLs as custom link types — for example, rel="my-custom-type" instead of a full URL like rel="https://example.com/my-custom-type".
This matters because browsers and other user agents rely on recognized rel values to determine how to handle linked resources. An unrecognized value will simply be ignored, which could mean your stylesheet doesn’t load, your prefetch hint doesn’t work, or search engines don’t respect your intended link relationship. Using correct values ensures predictable behavior across all browsers and tools.
Examples
Incorrect: Misspelled keyword
<link rel="styleshet" href="main.css">
The validator reports that styleshet is not a recognized keyword and is not an absolute URL.
Correct: Fixed spelling
<link rel="stylesheet" href="main.css">
Incorrect: Non-standard keyword on an anchor
<a href="https://example.com" rel="external">Visit Example</a>
The value external is not a standard rel keyword in the HTML specification, so the validator flags it.
Correct: Using a recognized keyword
<a href="https://example.com" rel="noopener">Visit Example</a>
Incorrect: Relative URL as a custom link type
<link rel="my-custom-rel" href="data.json">
Correct: Absolute URL as a custom link type
If you genuinely need a custom relationship type, provide a full absolute URL:
<link rel="https://example.com/rels/my-custom-rel" href="data.json">
Correct: Common valid rel values
Here are some frequently used standard rel keywords with their appropriate elements:
<!-- Linking a stylesheet -->
<link rel="stylesheet" href="styles.css">
<!-- Linking a favicon -->
<link rel="icon" href="favicon.ico">
<!-- Preloading a resource -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Telling search engines not to follow a link -->
<a href="https://example.com" rel="nofollow">Sponsored link</a>
<!-- Opening a link safely in a new tab -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External site</a>
Multiple rel values
You can specify multiple space-separated rel values. Each one must individually be either a recognized keyword or a valid absolute URL:
<!-- Correct: both values are recognized keywords -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
<!-- Incorrect: "popup" is not a recognized keyword or absolute URL -->
<a href="https://example.com" target="_blank" rel="noopener popup">External</a>
To resolve this error, consult the MDN rel attribute reference for the full list of recognized keywords and which elements support them. If your value isn’t on the list, either replace it with the correct standard keyword or use a complete absolute URL to define your custom link type.
In HTML, boolean attributes like required work differently from what many developers expect. Their presence on an element means the value is true, and their absence means the value is false. According to the WHATWG HTML specification, a boolean attribute’s value must either be the empty string or an ASCII case-insensitive match for the attribute’s canonical name. For the required attribute, this means the only valid values are "" (empty string) and "required".
A common mistake is writing required="true" or required="false". The value "true" is not a valid boolean attribute value in HTML and will trigger this validation error. Even more confusingly, writing required="false" does not make the input optional — since the attribute is still present, the browser still treats the field as required. This can lead to subtle bugs where a form field appears to be optional in your code but is actually enforced as required by the browser.
This matters for several reasons:
- Standards compliance: Invalid attribute values violate the HTML specification and will cause W3C validation errors.
- Code clarity: Using non-standard values like "true" or "false" misleads other developers about how the attribute works.
- Unexpected behavior: required="false" still makes the field required, which can cause confusing form behavior.
To make a field optional, simply remove the required attribute entirely rather than setting it to "false".
Examples
Incorrect: Invalid values for required
These will all trigger the “Bad value for attribute required“ validation error:
<input type="text" required="true">
<input type="email" required="false">
<input type="text" required="yes">
<input type="text" required="1">
Correct: Valid uses of the required attribute
All three of these forms are valid and make the input required:
<!-- Preferred: no value (most concise) -->
<input type="text" required>
<!-- Also valid: empty string -->
<input type="text" required="">
<!-- Also valid: canonical name as value -->
<input type="text" required="required">
Correct: Making a field optional
To make a field optional, remove the attribute entirely:
<input type="text">
Full form example
<form action="/submit" method="post">
<label for="name">Name (required):</label>
<input type="text" id="name" name="name" required>
<label for="notes">Notes (optional):</label>
<input type="text" id="notes" name="notes">
<button type="submit">Submit</button>
</form>
This same rule applies to other boolean attributes in HTML, such as disabled, checked, readonly, multiple, and autofocus. They all follow the same pattern: use the attribute with no value, with an empty string, or with the attribute’s own name as the value. Never assign "true" or "false" to them.
The sandbox attribute is one of the most powerful security features available for <iframe> elements. When present with no value (or with specific tokens), it applies a strict set of restrictions to the embedded content — blocking scripts, form submissions, popups, same-origin access, and more. You selectively relax these restrictions by adding space-separated tokens like allow-scripts, allow-forms, or allow-popups.
The problem arises specifically when allow-scripts and allow-same-origin are used together. The allow-scripts token lets the embedded page execute JavaScript, while allow-same-origin lets it retain its original origin (rather than being treated as coming from a unique, opaque origin). With both enabled, the embedded page’s JavaScript can access the parent page’s DOM via window.parent (if they share the same origin) or, more critically, can use JavaScript to programmatically remove the sandbox attribute from its own <iframe> element. Once the attribute is removed, all sandboxing restrictions are lifted, making the sandbox attribute completely pointless.
This is why the W3C validator flags this combination — it’s not technically invalid HTML, but it’s a security anti-pattern that renders sandboxing ineffective. The WHATWG HTML specification explicitly warns against this combination for the same reason.
How to fix it
The fix depends on what the embedded content actually needs:
-
If the embedded page needs scripts but not same-origin access: Remove allow-same-origin. The page can still run JavaScript but will be treated as a unique origin, preventing it from accessing cookies, storage, or the parent frame’s DOM.
-
If the embedded page needs same-origin access but not scripts: Remove allow-scripts. The page retains its origin but cannot execute any JavaScript.
-
If you believe both are required: Reconsider whether sandboxing is the right approach. If the embedded content truly needs both script execution and same-origin access, the sandbox attribute provides no meaningful security benefit, and you may want to use other security mechanisms like Content-Security-Policy headers instead.
Always follow the principle of least privilege — only grant the permissions the embedded content strictly requires.
Examples
❌ Bad: using both allow-scripts and allow-same-origin
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-same-origin">
</iframe>
This triggers the validator warning because the embedded page can use its script access combined with its preserved origin to break out of the sandbox entirely.
❌ Bad: both flags present alongside other tokens
<iframe
src="https://example.com/widget"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups">
</iframe>
Even with additional tokens, the presence of both allow-scripts and allow-same-origin still defeats the sandbox.
✅ Good: allowing scripts without same-origin access
<iframe
src="https://example.com/widget"
sandbox="allow-scripts">
</iframe>
The embedded page can run JavaScript but is treated as a unique opaque origin, preventing it from accessing parent-page resources or removing its own sandbox.
✅ Good: allowing only the necessary permissions
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-forms allow-popups">
</iframe>
This grants script execution, form submission, and popup capabilities while keeping the content sandboxed in a unique origin.
✅ Good: using an empty sandbox for maximum restriction
<iframe
src="https://example.com/widget"
sandbox="">
</iframe>
An empty sandbox attribute applies all restrictions — no scripts, no form submissions, no popups, no same-origin access, and more. This is the most secure option when the embedded content is purely static.
✅ Good: removing the sandbox when it provides no real benefit
<iframe
src="https://example.com/widget"
title="Example widget">
</iframe>
If you genuinely need both script execution and same-origin access, it’s more honest to omit sandbox entirely and rely on other security measures, rather than including an attribute that provides a false sense of security.
The sizes attribute works together with srcset to help the browser choose the most appropriate image source for the current layout. It accepts a comma-separated list of entries, where each entry is an optional media condition followed by a CSS length value (called a “source size value”). The browser evaluates these entries to determine the intended display width of the image before it downloads it. Valid length values include viewport-relative units like 100vw, absolute units like 472px, or calc() expressions like calc(100vw - 2rem).
The value auto was not part of the original HTML specification for the sizes attribute. However, the sizes="auto" value has been added to the HTML living standard specifically for use with lazy-loaded images (loading="lazy"). When both loading="lazy" and sizes="auto" are present, the browser can defer size calculation until layout time, since the image won’t be fetched immediately anyway. Some validators may not yet recognize this newer addition, or the error may appear because auto is being used without loading="lazy", or combined incorrectly with other size entries like sizes="auto, 100vw".
This validation error matters for several reasons. First, if the browser doesn’t understand the sizes value, it may fall back to a default of 100vw, which could cause it to download a larger image than necessary, hurting performance. Second, malformed attribute values can lead to unpredictable behavior across different browsers. Third, standards compliance ensures your markup works reliably now and in the future.
How to Fix
You have a few options depending on your situation:
-
Replace auto with a valid CSS length. If you know the intended display size of the image, specify it directly. This is the most broadly compatible approach.
-
Use sizes="auto" only with loading="lazy". If you want the browser to automatically determine the size, ensure you also include loading="lazy" and a width attribute on the image. Note that some validators may still flag this until they update their rules.
-
Remove auto from a comma-separated list. If you have something like sizes="auto, (max-width: 600px) 100vw, 50vw", remove the auto entry entirely.
Examples
Incorrect: Using auto without lazy loading
This triggers the validation error because auto is not a valid CSS length in the traditional sizes syntax.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="auto, 100vw"
alt="A scenic mountain landscape"
>
Fixed: Using a valid CSS length value
Replace auto with a concrete size or a set of media-conditioned sizes.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="(max-width: 472px) 100vw, 472px"
alt="A scenic mountain landscape"
>
In this example, when the viewport is 472 pixels wide or smaller, the image takes up the full viewport width (100vw). For wider viewports, the browser knows the image will display at 472px wide and selects the best source from srcset accordingly.
Fixed: Using auto with lazy loading
If you want the browser to determine the display size automatically, pair sizes="auto" with loading="lazy" and explicit dimensions.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="auto"
width="600"
height="400"
loading="lazy"
alt="A scenic mountain landscape"
>
The width and height attributes help the browser reserve the correct space in the layout, and loading="lazy" allows the browser to defer both loading and size calculation until the image is near the viewport.
Fixed: Using calc() for dynamic sizing
If your image sits inside a container with padding, you can use calc() for a precise size hint.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="calc(100vw - 2rem)"
alt="A scenic mountain landscape"
>
The sizes attribute tells the browser how wide an image will be displayed under different viewport conditions, allowing it to choose the best candidate from srcset before the page layout is calculated. Its value is a comma-separated list where each entry (except optionally the last) pairs a media condition with a CSS length. The final entry can be a bare length that serves as the default fallback. The full syntax looks like this:
(media-condition) length, (media-condition) length, fallback-length
Media conditions use the same grammar as CSS media queries. They must be enclosed in parentheses and can use logical operators like and, or, and not to combine sub-expressions. The slot size (length) that follows each condition must use a valid CSS length unit such as px, em, rem, vw, vh, vmin, vmax, or ch. Percentages are not valid in sizes slot lengths, and unitless numbers (other than 0) are also invalid.
This error matters for several reasons. First, if the browser cannot parse the sizes value, it falls back to a default of 100vw, which may cause it to download an unnecessarily large image — hurting performance and wasting bandwidth. Second, an invalid sizes attribute signals a misunderstanding of responsive image markup that could lead to layout or rendering issues. Third, standards-compliant HTML ensures consistent behavior across all browsers.
Common Parse Errors
Here are the most frequent mistakes that trigger this validation error:
- Missing parentheses around the media condition. Writing min-width: 600px instead of (min-width: 600px).
- Missing slot size after a condition. Each media condition must be followed by a length value, e.g., (min-width: 600px) 50vw. Writing (min-width: 600px), 100vw without a slot size after the condition is invalid — the browser interprets (min-width: 600px) alone as the entry.
- Using percentages as slot sizes. Values like 50% are not allowed; use viewport-relative units like 50vw instead.
- Unitless numbers. A slot size of 300 is invalid; it must be 300px or another valid length.
- Trailing or extra commas. A trailing comma after the last entry or double commas between entries will cause a parse error.
- Mixing up range syntax. Modern media query range syntax like (width >= 600px) is valid, but hybrid forms like (min-width <= 600px) are not. Use either the traditional (min-width: 600px) or the range form (width >= 600px).
- Invalid tokens or typos. Misspelled feature names (e.g., min-widht) or unsupported tokens will break parsing.
How to Fix
- Wrap every media condition in parentheses. Even simple conditions need them: (min-width: 600px).
- Follow each condition with a valid length. For example, (min-width: 600px) 50vw.
- End with a fallback length. The last item should be a bare length with no media condition, like 100vw.
- Use valid CSS length units for slot sizes — px, em, rem, vw, vh, etc. Never use %.
- Ensure your srcset uses w descriptors that match the intrinsic pixel widths of the image files, since sizes only works with width descriptors.
Examples
Incorrect: missing parentheses and missing slot size
The media condition lacks parentheses, and there is no slot size telling the browser how wide the image will be when the condition matches.
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="min-width: 600px, 100vw"
alt="A mountain landscape">
Correct: parentheses and slot size added
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A mountain landscape">
Incorrect: percentage used as slot size
<img
src="banner-1200.jpg"
srcset="banner-600.jpg 600w, banner-1200.jpg 1200w"
sizes="(min-width: 768px) 50%, 100%"
alt="Promotional banner">
Correct: viewport units instead of percentages
<img
src="banner-1200.jpg"
srcset="banner-600.jpg 600w, banner-1200.jpg 1200w"
sizes="(min-width: 768px) 50vw, 100vw"
alt="Promotional banner">
Incorrect: trailing comma
<img
src="icon-256.png"
srcset="icon-128.png 128w, icon-256.png 256w"
sizes="(min-width: 1024px) 128px, 64px,"
alt="App icon">
Correct: trailing comma removed
<img
src="icon-256.png"
srcset="icon-128.png 128w, icon-256.png 256w"
sizes="(min-width: 1024px) 128px, 64px"
alt="App icon">
Incorrect: invalid range syntax mixing traditional and modern forms
<img
src="hero-1600.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(min-width <= 600px) 100vw, 50vw"
alt="Hero image">
Correct: using proper range syntax
<img
src="hero-1600.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(width <= 600px) 100vw, 50vw"
alt="Hero image">
Multiple conditions with a fallback
This example shows a fully valid sizes attribute with several breakpoints, combining traditional and modern range syntax across entries.
<img
src="article-1200.jpg"
srcset="article-400.jpg 400w, article-800.jpg 800w, article-1200.jpg 1200w"
sizes="(min-width: 1200px) 33vw, (min-width: 768px) 50vw, 100vw"
alt="Article thumbnail">
Using calc() in slot sizes
You can use calc() for more precise slot sizes. This is fully valid in the sizes attribute.
<img
src="card-800.jpg"
srcset="card-400.jpg 400w, card-800.jpg 800w"
sizes="(min-width: 960px) calc(33.33vw - 2rem), calc(100vw - 2rem)"
alt="Product card">
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