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 color property, along with properties like background-color, border-color, and outline-color, expects values that conform to the CSS Color specification. The validator triggers this error when it encounters something that doesn’t match any valid color syntax. Common causes include:
- Plain numbers like 0 or 123 — numbers alone aren’t colors.
- Typos in color keywords such as grean instead of green, or trasparent instead of transparent.
- Malformed hex values like #GGG (invalid hex characters) or #12345 (wrong number of digits — hex colors must be 3, 4, 6, or 8 digits).
- Incorrect function syntax such as rgb(255 255 255 / 50) missing the % on the alpha value, or using legacy commas mixed with modern space-separated syntax.
- Missing units or hash symbols like 000000 instead of #000000.
This matters because browsers handle invalid color values unpredictably. Most will simply ignore the declaration entirely, which means the element inherits its color from a parent or falls back to the browser default — potentially making text unreadable against its background. Writing valid CSS ensures consistent rendering across all browsers and improves the maintainability of your code.
Valid CSS color formats
CSS supports several color formats:
| Format | Example | Notes |
|---|---|---|
| Named colors | red, blue, transparent | 148 predefined keywords |
| Hexadecimal | #ff0000, #f00 | 3, 4, 6, or 8 digits |
| rgb() / rgba() | rgb(255, 0, 0) | Comma or space-separated |
| hsl() / hsla() | hsl(0, 100%, 50%) | Hue, saturation, lightness |
| currentcolor | currentcolor | Inherits the current color value |
Examples
Invalid: plain number as a color
A bare number is not a recognized color value:
<style>
.example {
color: 0;
}
</style>
Invalid: typo in a color keyword
<style>
.example {
background-color: trasparent;
}
</style>
Invalid: hex value missing the # prefix
<style>
.example {
color: 000000;
}
</style>
Invalid: hex value with wrong digit count
<style>
.example {
color: #12345;
}
</style>
Fixed: using a named color keyword
<style>
.example {
color: black;
}
</style>
Fixed: using a hexadecimal color
<style>
.example {
color: #000000;
}
</style>
Fixed: using rgb()
<style>
.example {
color: rgb(0, 0, 0);
}
</style>
Fixed: using hsl()
<style>
.example {
color: hsl(0, 0%, 0%);
}
</style>
Fixed: using rgba() for semi-transparent color
<style>
.example {
color: rgba(0, 0, 0, 0.5);
}
</style>
Fixed: correcting the transparent keyword typo
<style>
.example {
background-color: transparent;
}
</style>
If you’re unsure whether a value is valid, browser DevTools can help — most browsers will strike through or ignore invalid property values in the Styles panel, giving you a quick visual indicator of the problem.
The CSS display property controls how an element generates boxes in the layout. It determines whether an element behaves as a block-level or inline-level element and defines the layout model for its children (e.g., flow, flexbox, or grid). Because display is fundamental to page layout, using an invalid value means the browser will ignore the declaration entirely, potentially causing unexpected rendering.
Common causes of this error include:
- Typos — writing dipslay: block, display: blok, or display: flx instead of the correct keywords.
- Confusing values from other properties — using values like center, hidden, absolute, or relative, which belong to properties like text-align, visibility, or position, not display.
- Invented or outdated values — using non-standard or deprecated values that browsers don’t recognize, such as display: box (an old prefixed flexbox syntax without the prefix).
- Missing vendor prefixes — some older syntaxes like -webkit-flex were valid in certain browsers but are not standard CSS values.
The valid values for display include: block, inline, inline-block, flex, inline-flex, grid, inline-grid, flow-root, none, contents, table, table-row, table-cell, table-caption, table-column, table-column-group, table-footer-group, table-header-group, table-row-group, list-item, and the multi-keyword syntax like block flow, block flex, or inline grid.
Using invalid CSS values is a problem because browsers silently discard declarations they don’t understand. This means your intended layout won’t be applied, and debugging can be difficult since no visible error appears in the browser. Validating your CSS catches these mistakes early.
Examples
Invalid: typo in the display value
<div style="display: flx;">
<p>This container was meant to be a flex container.</p>
</div>
Fixed: correct flex value
<div style="display: flex;">
<p>This container is now a flex container.</p>
</div>
Invalid: using a value from another property
<nav style="display: center;">
<a href="/">Home</a>
</nav>
The value center does not belong to display. If the goal is to center content, use a valid display value combined with appropriate alignment properties.
Fixed: using flex with centering
<nav style="display: flex; justify-content: center;">
<a href="/">Home</a>
</nav>
Invalid: using a position value instead of a display value
<div style="display: absolute;">
<p>Overlay content</p>
</div>
Fixed: using the correct property
<div style="position: absolute; display: block;">
<p>Overlay content</p>
</div>
Invalid: using a non-standard value
<ul style="display: box;">
<li>Item 1</li>
<li>Item 2</li>
</ul>
Fixed: using the standard flexbox value
<ul style="display: flex;">
<li>Item 1</li>
<li>Item 2</li>
</ul>
The left property specifies the horizontal offset of a positioned element — one that has its position set to relative, absolute, fixed, or sticky. The W3C validator checks CSS within style attributes and <style> elements, and it will flag any value it cannot recognize as a valid left value.
Common causes of this error include:
- Misspelled or non-existent units: Writing 10 px (with a space), 10pixels, or 20ppx instead of 10px.
- Unsupported keywords: Using values like none, center, or middle, which are not valid for the left property.
- Missing units on non-zero numbers: Writing left: 10 instead of left: 10px. Zero is the only number that doesn’t require a unit.
- Typos in keyword values: Writing auто or autoo instead of auto.
- CSS custom properties in inline styles: Using var(--offset) in a style attribute may trigger validation warnings depending on the validator’s CSS level.
The valid values for the left property are:
- <length>: A numeric value with a unit, such as 10px, 2em, 3rem, 1vw.
- <percentage>: A percentage relative to the containing block’s width, such as 50%.
- auto: Lets the browser determine the position (this is the default).
- Global keywords: inherit, initial, unset, and revert.
Using an invalid value means the browser will ignore the declaration entirely, which can break your layout. Fixing these values ensures consistent rendering across browsers and compliance with CSS standards.
Examples
Invalid: Using an unsupported keyword
The keyword none is not a valid value for the left property.
<div style="position: absolute; left: none;">Positioned element</div>
Invalid: Missing unit on a non-zero number
A bare number (other than 0) is not valid without a CSS unit.
<div style="position: relative; left: 20;">Shifted element</div>
Invalid: Misspelled unit
The unit xp does not exist in CSS.
<div style="position: absolute; left: 15xp;">Positioned element</div>
Valid: Using a length value
<div style="position: absolute; left: 20px;">20 pixels from the left</div>
Valid: Using a percentage
<div style="position: absolute; left: 50%;">Offset by 50% of containing block</div>
Valid: Using the auto keyword
<div style="position: absolute; left: auto;">Browser-determined position</div>
Valid: Using zero without a unit
Zero does not require a unit in CSS.
<div style="position: absolute; left: 0;">Flush with the left edge</div>
Valid: Using inherit
<div style="position: relative; left: inherit;">Inherits left value from parent</div>
To fix this error, identify the invalid value the validator is reporting and replace it with one of the accepted value types listed above. If you intended to reset the position, use auto or 0. If you meant to remove a previously set left value, use initial or unset rather than an unsupported keyword like none.
The mask CSS shorthand property allows you to partially or fully hide portions of an element by applying a graphical mask. It is a shorthand for several sub-properties including mask-image, mask-mode, mask-repeat, mask-position, mask-clip, mask-origin, mask-size, and mask-composite. Because it’s a shorthand, each value you provide must correspond to one of these sub-properties’ accepted values. The validator triggers this error when it encounters a value that doesn’t fit any of them — for example, an arbitrary keyword, a misspelled function name, or an unsupported syntax.
Common causes of this error include:
- Arbitrary keywords — Using made-up names like star-shape or circle-mask that aren’t valid CSS values.
- Misspelled functions or keywords — Typos such as lnear-gradient() instead of linear-gradient(), or noen instead of none.
- Browser-prefixed values without the standard value — Using -webkit-mask syntax or values that don’t align with the standard mask property.
- Invalid shorthand combinations — Providing sub-property values in an order or combination the shorthand doesn’t accept.
- Missing url() wrapper — Referencing an image file path directly without wrapping it in the url() function.
This matters for standards compliance because browsers may silently ignore invalid mask values, resulting in the mask not being applied at all. Your design could look completely different than intended, and the failure may be hard to debug without validation.
Valid mask values
The mask property accepts one or more comma-separated mask layers. Each layer can include:
- none — No mask is applied.
- url() — A reference to an SVG mask element or an image file (e.g., url(mask.svg), url(mask.png)).
- CSS image functions — Such as linear-gradient(), radial-gradient(), conic-gradient(), image(), etc.
- Geometry box keywords (for mask-clip / mask-origin) — Such as content-box, padding-box, border-box, fill-box, stroke-box, view-box.
- Compositing keywords (for mask-composite) — Such as add, subtract, intersect, exclude.
Examples
Incorrect: arbitrary keyword as a mask value
<div style="mask: star-shape;">
Masked Content
</div>
The value star-shape is not a recognized mask value and will be rejected by the validator.
Incorrect: missing url() function
<div style="mask: star.svg;">
Masked Content
</div>
A bare file path is not valid. Image references must be wrapped in the url() function.
Correct: using url() to reference a mask image
<div style="mask: url(star.svg);">
Masked Content
</div>
Correct: using none to explicitly disable masking
<div style="mask: none;">
No Mask Applied
</div>
Correct: using a gradient as a mask
<div style="mask: linear-gradient(to right, transparent, black);">
Fading Content
</div>
Correct: combining multiple shorthand values
<div style="mask: url(mask.png) no-repeat center / contain;">
Masked Content
</div>
This sets the mask image, repeat behavior, position, and size in a single shorthand declaration.
Correct: multiple mask layers
<div style="mask: url(shape.svg) no-repeat, linear-gradient(to bottom, black, transparent);">
Multi-layer Mask
</div>
When fixing this error, double-check your value against the CSS Masking specification on MDN. If you’re using vendor-prefixed versions like -webkit-mask, also ensure the standard mask property is present with valid values for forward compatibility.
The id attribute uniquely identifies an element within a document. According to the WHATWG HTML living standard, if the id attribute is specified, its value must be non-empty and must not contain any ASCII whitespace characters. The attribute itself is optional — you don’t need to include it — but when you do, it must have a valid value. Setting id="" violates this rule because the empty string is not a valid identifier.
This issue commonly occurs when code is generated dynamically (e.g., by a templating engine or JavaScript framework) and the variable intended to populate the id value resolves to an empty string. It can also happen when developers add the attribute as a placeholder and forget to fill it in.
Why this matters
- Standards compliance: An empty id violates the HTML specification, making your document invalid.
- Accessibility: Assistive technologies like screen readers rely on id attributes to associate <label> elements with form controls. An empty id breaks this association, making forms harder to use for people who depend on these tools.
- JavaScript and CSS: Methods like document.getElementById("") and selectors like # (with no identifier) will not work as expected. An empty id can cause subtle, hard-to-debug issues in your scripts and styles.
- Browser behavior: While browsers are generally forgiving, an empty id leads to undefined behavior. Different browsers may handle it inconsistently.
How to fix it
- Assign a meaningful value: Give the id a descriptive, unique value that identifies the element’s purpose (e.g., id="country-select").
- Remove the attribute: If you don’t need the id, simply remove it from the element altogether.
- Fix dynamic generation: If a templating engine or framework is producing the empty value, add a conditional check to either output a valid id or omit the attribute entirely.
Examples
❌ Incorrect: empty id attribute
<label for="country">Country</label>
<select id="" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
This triggers the validation error because id="" is an empty string.
✅ Correct: meaningful id value
<label for="country">Country</label>
<select id="country" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
The id now has a valid, non-empty value, and the <label> element’s for attribute correctly references it.
✅ Correct: id attribute removed entirely
<label>
Country
<select name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
</label>
If you don’t need the id, remove it. Here, the <label> wraps the <select> directly, so the for/id association isn’t needed — the implicit label works just as well.
The aria-activedescendant attribute tells assistive technologies which child element within a composite widget — such as a combobox, listbox, or autocomplete dropdown — is currently “active” or focused. Instead of moving actual DOM focus to each option, the parent element (like an input) retains focus while aria-activedescendant points to the visually highlighted option by referencing its id. This allows screen readers to announce the active option without disrupting keyboard interaction on the input.
When aria-activedescendant is set to an empty string (""), it creates an invalid state. The HTML and ARIA specifications require that any ID reference attribute either contains a valid, non-empty ID token or is omitted altogether. An empty string is not a valid ID, so the W3C validator flags this as an error: Bad value “” for attribute “aria-activedescendant” on element “input”: An ID must not be the empty string.
This problem commonly occurs in JavaScript-driven widgets where aria-activedescendant is cleared by setting it to "" when no option is highlighted — for example, when a dropdown closes or the user clears their selection. While the developer’s intent is correct (indicating that nothing is active), the implementation is wrong.
Why this matters
- Accessibility: Screen readers may behave unpredictably when encountering an empty ID reference. Some may silently ignore it, while others may announce errors or fail to convey widget state correctly.
- Standards compliance: The ARIA specification explicitly requires ID reference values to be non-empty strings that match an existing element’s id.
- Browser consistency: Browsers handle invalid ARIA attributes inconsistently, which can lead to different experiences across platforms and assistive technologies.
How to fix it
- Remove the attribute when no descendant is active. Use removeAttribute('aria-activedescendant') in JavaScript instead of setting it to an empty string.
- Set a valid ID when a descendant becomes active, pointing to the id of the currently highlighted or selected option.
- Never render the attribute in HTML with an empty value. If your framework or templating engine conditionally renders attributes, ensure it omits the attribute entirely rather than outputting aria-activedescendant="".
Examples
Incorrect: empty string value
This triggers the W3C validation error because the attribute value is an empty string.
<input type="text" role="combobox" aria-activedescendant="" />
Correct: attribute omitted when no option is active
When nothing is active, simply leave the attribute off.
<input type="text" role="combobox" aria-expanded="false" />
Correct: valid ID reference when an option is active
When a user highlights an option, set aria-activedescendant to that option’s id.
<div role="combobox">
<input
type="text"
role="combobox"
aria-expanded="true"
aria-controls="suggestions"
aria-activedescendant="option2" />
<ul id="suggestions" role="listbox">
<li id="option1" role="option">Apple</li>
<li id="option2" role="option" aria-selected="true">Banana</li>
<li id="option3" role="option">Cherry</li>
</ul>
</div>
Correct: managing the attribute dynamically with JavaScript
The key fix in JavaScript is using removeAttribute instead of setting the value to an empty string.
<div role="combobox">
<input
id="search"
type="text"
role="combobox"
aria-expanded="true"
aria-controls="results" />
<ul id="results" role="listbox">
<li id="result1" role="option">First result</li>
<li id="result2" role="option">Second result</li>
</ul>
</div>
<script>
const input = document.getElementById('search');
function setActiveOption(optionId) {
if (optionId) {
input.setAttribute('aria-activedescendant', optionId);
} else {
// Remove the attribute instead of setting it to ""
input.removeAttribute('aria-activedescendant');
}
}
</script>
In summary, always ensure aria-activedescendant either points to a real, non-empty id or is removed from the element. Never set it to an empty string.
The <textarea> element represents a multi-line plain-text editing control, commonly used for comments, feedback forms, and other free-form text input. The rows attribute specifies the number of visible text lines the textarea should display. According to the HTML specification, when the rows attribute is present, its value must be a valid positive integer — meaning a string of one or more digits representing a number greater than zero (e.g., 1, 5, 20). An empty string ("") does not meet this requirement.
This issue typically occurs when a template engine, CMS, or JavaScript framework dynamically sets the rows attribute but outputs an empty value instead of a number, or when a developer adds the attribute as a placeholder intending to fill it in later.
Why this matters
- Standards compliance: The HTML specification explicitly requires rows to be a positive integer when present. An empty string violates this rule.
- Unpredictable rendering: Browsers fall back to a default value (typically 2) when they encounter an invalid rows value, but this behavior isn’t guaranteed to be consistent across all browsers and versions.
- Maintainability: Invalid attributes can mask bugs in dynamic code that was supposed to provide a real value.
How to fix it
You have two options:
- Set a valid positive integer: Replace the empty string with a number like 4, 5, or whatever suits your design.
- Remove the attribute: If you don’t need to control the number of visible rows (or prefer to handle sizing with CSS), simply omit the rows attribute. The browser will use its default.
If the value is generated dynamically, ensure your code has a fallback so it never outputs an empty string. You can also control the textarea’s height using CSS (height or min-height) instead of relying on the rows attribute.
Examples
❌ Invalid: empty string for rows
<textarea name="comments" rows="" cols="25">
</textarea>
This triggers the error because "" is not a valid positive integer.
✅ Fixed: providing a valid positive integer
<textarea name="comments" rows="5" cols="25">
</textarea>
Setting rows="5" tells the browser to display five visible lines of text.
✅ Fixed: removing the attribute entirely
<textarea name="comments" cols="25">
</textarea>
When rows is omitted, the browser uses its default (typically 2 rows). This is perfectly valid.
✅ Alternative: using CSS for sizing
<textarea name="comments" style="height: 10em; width: 25ch;">
</textarea>
If you need precise control over the textarea’s dimensions, CSS properties like height, min-height, and width give you more flexibility than the rows and cols attributes. In this case, you can safely leave both attributes off.
The HTML living standard defines that scripts with type="module" are always fetched in parallel and evaluated after the document has been parsed, which is the same behavior that the defer attribute provides for classic scripts. Because this deferred execution is an inherent characteristic of module scripts, the spec explicitly forbids combining the two. Including both doesn’t change how the browser handles the script, but it signals a misunderstanding of how modules work and produces invalid HTML.
This validation error commonly arises when developers migrate classic scripts to ES modules. A classic script like <script defer src="app.js"></script> relies on the defer attribute to avoid blocking the parser. When converting to a module by adding type="module", it’s natural to leave defer in place — but it’s no longer needed or allowed.
It’s worth noting that the async attribute is valid on module scripts and does change their behavior. While defer is redundant because modules are already deferred, async overrides that default and causes the module to execute as soon as it and its dependencies have finished loading, rather than waiting for HTML parsing to complete.
How to Fix
Remove the defer attribute from any <script> element that has type="module". No other changes are needed — the loading and execution behavior will remain identical.
If you intentionally want the script to run as soon as possible (before parsing completes), use async instead of defer. But if you want the standard deferred behavior, simply omit both attributes and let the module default take effect.
Examples
❌ Incorrect: defer combined with type="module"
<script type="module" defer src="app.js"></script>
The defer attribute is redundant here and causes a validation error.
✅ Correct: module script without defer
<script type="module" src="app.js"></script>
Module scripts are deferred automatically, so this behaves exactly the same as the incorrect example above but is valid HTML.
✅ Correct: using async with a module (when needed)
<script type="module" async src="analytics.js"></script>
Unlike defer, the async attribute is permitted on module scripts. It causes the module to execute as soon as it’s ready, without waiting for HTML parsing to finish.
✅ Correct: classic script with defer
<script defer src="app.js"></script>
For classic (non-module) scripts, the defer attribute is valid and necessary if you want deferred execution.
Hidden inputs are designed to carry data between the client and server without any user interaction or visual presence. The browser does not render them, screen readers do not announce them, and they are entirely excluded from the accessibility tree. Because aria-* attributes exist solely to convey information to assistive technologies, adding them to an element that assistive technologies cannot perceive is contradictory and meaningless.
The HTML specification explicitly prohibits aria-* attributes on input elements with type="hidden". This restriction exists because WAI-ARIA attributes — such as aria-label, aria-invalid, aria-describedby, aria-required, and all others in the aria-* family — are meant to enhance the accessible representation of interactive or visible elements. A hidden input has no such representation, so these attributes have nowhere to apply.
This issue commonly arises when:
- JavaScript frameworks or templating engines apply aria-* attributes indiscriminately to all form inputs, regardless of type.
- A developer changes an input’s type from "text" to "hidden" but forgets to remove the accessibility attributes that were relevant for the visible version.
- Form libraries or validation plugins automatically inject attributes like aria-invalid onto every input in a form.
To fix the issue, simply remove all aria-* attributes from any input element that has type="hidden". If the aria-* attribute was meaningful on a previously visible input, no replacement is needed — the hidden input doesn’t participate in the user experience at all.
Examples
Incorrect: hidden input with aria-invalid
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com" aria-invalid="false">
<button type="submit">Submit</button>
</form>
Correct: hidden input without aria-* attributes
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com">
<button type="submit">Submit</button>
</form>
Incorrect: hidden input with multiple aria-* attributes
<form action="/save" method="post">
<input
type="hidden"
name="session_token"
value="abc123"
aria-label="Session token"
aria-required="true"
aria-describedby="token-help">
<button type="submit">Save</button>
</form>
Correct: all aria-* attributes removed
<form action="/save" method="post">
<input type="hidden" name="session_token" value="abc123">
<button type="submit">Save</button>
</form>
Correct: aria-* attributes on a visible input (where they belong)
If the input is meant to be visible and accessible, use an appropriate type value instead of "hidden":
<form action="/login" method="post">
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
aria-required="true"
aria-invalid="false"
aria-describedby="username-help">
<p id="username-help">Enter your registered email or username.</p>
<button type="submit">Log in</button>
</form>
According to the HTML specification, the content model for <ul> is strictly limited to zero or more <li> elements. Any text node placed directly inside the <ul> violates this rule, even if it seems harmless or invisible. Browsers may still render the page, but the resulting DOM structure is technically invalid and can lead to unpredictable behavior across different browsers and assistive technologies.
This matters for accessibility because screen readers rely on proper list structure to announce the number of items and allow users to navigate between them. Stray text nodes inside a <ul> can confuse these tools, causing list items to be miscounted or the text to be read in an unexpected context.
There are several common scenarios that trigger this error:
Loose text used as a list title. Developers sometimes place a heading or label directly inside the <ul> to describe the list. This text must be moved outside the list element.
Stray or other entities between list items. This often happens in templating systems or when code is concatenated, where characters or other text nodes end up between <li> elements. These should be removed entirely, since spacing between list items should be controlled with CSS.
Accidentally placing inline content without wrapping it in <li>. Sometimes content that should be a list item is simply missing its <li> wrapper.
Examples
❌ Text used as a list title inside <ul>
<ul>
Fruits
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
</ul>
The word “Fruits” is a text node directly inside the <ul>, which is not allowed.
✅ Move the title outside the list
<h3>Fruits</h3>
<ul>
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
</ul>
Using a heading before the list is semantically clear. You can also use a <p> or <span> if a heading isn’t appropriate.
❌ entities between list items
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
Each is a text node sitting directly inside the <ul>, triggering the error.
✅ Remove the entities and use CSS for spacing
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
ul li {
margin-bottom: 0.5em;
}
Any visual spacing between list items should be handled with CSS margin or padding, not with HTML entities.
❌ Unwrapped content that should be a list item
<ul>
<li>Milk</li>
Eggs
<li>Bread</li>
</ul>
✅ Wrap the content in an <li> element
<ul>
<li>Milk</li>
<li>Eggs</li>
<li>Bread</li>
</ul>
The same rules apply to <ol> (ordered lists) and <menu> elements — their direct children must be <li> elements, and text nodes are not permitted. If your list is generated dynamically by a templating engine or JavaScript, check the output carefully for stray whitespace or text that may have been injected between list items.
Direct text nodes inside select are not permitted content. Browsers typically ignore or mangle that text, leading to inconsistent rendering and confusing experiences for screen reader users. It also breaks conformance, which can impact maintainability and automated tooling. The right approach is to keep instructional text in a corresponding label, or provide a non-selectable prompt using a disabled, selected option. Group labels must be provided with optgroup elements, not free text.
To fix it, remove any raw text inside the select. If you need a prompt, add a first option with value="" and disabled selected hidden for a placeholder-like experience, or rely on a visible label. Ensure all selectable items are wrapped in option, and any grouping uses optgroup with its label attribute. Always associate the select with a label via for/id for accessibility.
Examples
Triggers the error (text node inside select)
<select>
Please select an option:
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
Correct: move instructions to a label
<label for="flavor">Please select a flavor:</label>
<select id="flavor" name="flavor">
<option value="vanilla">Vanilla</option>
<option value="chocolate">Chocolate</option>
</select>
Correct: provide a non-selectable prompt inside select
<label for="country">Country</label>
<select id="country" name="country" required>
<option value="" disabled selected hidden>Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
Correct: use optgroup for grouping, not free text
<label for="city">City</label>
<select id="city" name="city">
<optgroup label="USA">
<option value="nyc">New York</option>
<option value="la">Los Angeles</option>
</optgroup>
<optgroup label="Canada">
<option value="toronto">Toronto</option>
<option value="vancouver">Vancouver</option>
</optgroup>
</select>
Correct: full document (for context)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Select without stray text</title>
</head>
<body>
<form>
<label for="size">Choose a size:</label>
<select id="size" name="size">
<option value="" disabled selected hidden>Select a size</option>
<option value="s">Small</option>
<option value="m">Medium</option>
<option value="l">Large</option>
</select>
</form>
</body>
</html>
Tips:
- Put instructions in a label or surrounding text, not inside select.
- Every choice must be an option; use optgroup with label to name groups.
- For placeholders, prefer a disabled, selected first option; avoid raw text nodes.
The background CSS property is a shorthand that can accept values for background-color, background-image, background-position, background-size, background-repeat, background-origin, background-clip, and background-attachment. When the validator encounters an unrecognized value, it tries to match it against individual sub-properties like background-color. If the value doesn’t match any of them, you’ll see this error.
Common causes include typos in color names (e.g., bleu instead of blue), malformed hex codes (e.g., #gggggg or a missing #), incorrect function syntax (e.g., rgb(255 0 0 with a missing parenthesis), or using values that simply don’t exist in CSS. This error can also appear when a CSS custom property (variable) is used in inline styles and the validator can’t resolve it, or when a browser-specific value is used that isn’t part of the CSS specification.
Fixing this issue ensures your styles render predictably across browsers. While browsers are often forgiving and may ignore invalid declarations silently, relying on that behavior can lead to inconsistent rendering. Standards-compliant CSS is easier to maintain and debug.
How to Fix
- Check for typos in color names, hex codes, or function syntax.
- Verify the value format — hex colors need a # prefix, rgb() and rgba() need proper comma-separated or space-separated values with closing parentheses.
- Use background-color instead of the shorthand background if you only intend to set a color. This makes your intent clearer and reduces the chance of conflicting shorthand values.
- Remove vendor-prefixed or non-standard values that the validator doesn’t recognize.
Examples
Incorrect — Typo in color name
<div style="background: aquaa;">Content</div>
aquaa is not a valid CSS color name, so the validator rejects it.
Correct — Valid color name
<div style="background: aqua;">Content</div>
Incorrect — Malformed hex code
<div style="background: #xyz123;">Content</div>
Hex color codes only allow characters 0–9 and a–f.
Correct — Valid hex code
<div style="background: #00a123;">Content</div>
Incorrect — Missing hash symbol
<div style="background: ff0000;">Content</div>
Without the #, the validator interprets ff0000 as an unknown keyword.
Correct — Hex code with hash
<div style="background: #ff0000;">Content</div>
Incorrect — Broken rgb() syntax
<div style="background: rgb(255, 0, 300);">Content</div>
RGB channel values must be between 0 and 255 (or 0% to 100%).
Correct — Valid rgb() value
<div style="background: rgb(255, 0, 128);">Content</div>
Correct — Using background-color for clarity
When you only need to set a color, prefer the specific background-color property over the shorthand:
<div style="background-color: rgba(255, 0, 0, 0.5);">Semi-transparent red</div>
Correct — Valid shorthand with image and other properties
<div style="background: url('image.jpg') no-repeat center / cover;">Content</div>
Note the / between background-position (center) and background-size (cover) — this is required syntax in the shorthand.
The padding-right property defines the space between an element’s content and its right border. According to the CSS Box Model specification, padding represents internal space within an element, and conceptually, negative internal space doesn’t make sense — you can’t have less than zero space between content and its border. This rule applies equally to all padding properties: padding-top, padding-right, padding-bottom, padding-left, and the padding shorthand.
Browsers will typically ignore or discard a negative padding value, meaning your intended layout adjustment won’t take effect. Beyond simply being invalid CSS, this can lead to inconsistent rendering across browsers and unexpected layout behavior. Relying on invalid values makes your stylesheets fragile and harder to maintain.
If your goal is to pull an element closer to its neighbor or create an overlapping effect, margin-right is the appropriate property to use. Unlike padding, margins are explicitly allowed to have negative values. Negative margins reduce the space between elements or even cause them to overlap, which is often the actual intent behind a negative padding attempt.
How to Fix
- Set the value to 0 or a positive number. If you simply want no padding, use 0. If you need some spacing, use a positive value.
- Use margin-right for negative spacing. If you need to reduce external space or create overlap, switch to a negative margin instead.
- Re-evaluate your layout approach. In some cases, using transform: translateX(), Flexbox gap, or Grid layout may achieve the desired result more cleanly than negative values on any property.
Examples
Incorrect: negative padding value
<style>
.sidebar {
padding-right: -10px;
}
</style>
<div class="sidebar">
<p>Sidebar content</p>
</div>
This triggers the validator error because -10px is not a valid value for padding-right.
Fixed: using zero or a positive value
<style>
.sidebar {
padding-right: 0;
}
</style>
<div class="sidebar">
<p>Sidebar content</p>
</div>
Fixed: using a negative margin instead
If the intent was to reduce external spacing on the right side, use margin-right:
<style>
.sidebar {
padding-right: 0;
margin-right: -10px;
}
</style>
<div class="sidebar">
<p>Sidebar content</p>
</div>
Fixed: using transform for visual offset
If the goal is to visually shift the element without affecting document flow, transform is another option:
<style>
.sidebar {
padding-right: 0;
transform: translateX(10px);
}
</style>
<div class="sidebar">
<p>Sidebar content</p>
</div>
Quick reference: padding vs. margin
| Property | Negative values allowed? | Purpose |
|---|---|---|
| padding-right | No | Space between content and border |
| margin-right | Yes | Space between the element’s border and surrounding elements |
Choose the property that matches your layout intent, and remember that all four padding directions — padding-top, padding-right, padding-bottom, and padding-left — follow the same non-negative rule.
The <iframe> element is designed to embed another browsing context (essentially a nested document) within the current page. According to the HTML specification, the content model of <iframe> is nothing — it must not contain any text nodes or child elements. Any text placed directly inside the <iframe> tags is invalid HTML.
Historically, some older HTML versions allowed fallback text inside <iframe> for browsers that didn’t support iframes. This is no longer the case in modern HTML. All current browsers support <iframe>, so there is no need for fallback content. The W3C validator flags this as an error because text or elements between the <iframe> tags violate the current HTML specification.
There are two valid ways to specify the content an iframe should display:
- src attribute — Provide a URL pointing to the document you want to embed.
- srcdoc attribute — Provide inline HTML content directly as the attribute’s value. The HTML is written as a string with appropriate character escaping (e.g., < for < and " for " if using double-quoted attributes).
If you were using text inside <iframe> as a description or fallback, consider using the title attribute instead to provide an accessible label for the embedded content.
Examples
❌ Invalid: text inside the <iframe> element
<iframe>Some content here</iframe>
<iframe><p>This is my embedded content.</p></iframe>
Both of these will trigger the “Text not allowed in element iframe in this context” error.
✅ Valid: using the src attribute
<iframe src="https://example.com" title="Example website"></iframe>
This loads the document at the specified URL inside the iframe.
✅ Valid: using the srcdoc attribute
<iframe srcdoc="<p>This is my embedded content.</p>" title="Inline content"></iframe>
This renders the inline HTML provided in the srcdoc attribute. Note that HTML special characters must be escaped when the attribute value is enclosed in double quotes.
✅ Valid: self-closing style (empty element)
<iframe src="https://example.com" title="Example website"></iframe>
The <iframe> element requires a closing tag, but nothing should appear between the opening and closing tags. Keeping the element empty is the correct approach.
✅ Valid: adding an accessible label with title
If your original intent was to describe the iframe’s content for users or assistive technologies, use the title attribute:
<iframe src="/embedded-report.html" title="Monthly sales report"></iframe>
The title attribute provides a label that screen readers can announce, improving the accessibility of the embedded content.
The box-shadow property applies one or more shadow effects to an element. Its syntax accepts several values in a flexible order:
box-shadow: <offset-x> <offset-y> <blur-radius>? <spread-radius>? <color>? inset?;
The <color> component is optional — if omitted, it defaults to currentcolor. However, when a color is provided, it must be a valid CSS color value. The validator raises this error when it encounters something in the color position that doesn’t match any recognized color format.
Common causes of this error include:
- Misspelled color names — e.g., greyy instead of grey, or balck instead of black.
- Invalid hex codes — e.g., #GGG or #12345 (hex codes must be 3, 4, 6, or 8 hex digits).
- Fabricated color names — e.g., banana or darkwhite, which are not part of the CSS named colors specification.
- Malformed function syntax — e.g., rgb(0,0,0,0.3) using the legacy comma syntax with four values instead of rgba(), or missing parentheses.
- Incorrect value order — placing values in an unexpected position can cause the validator to misinterpret a non-color value as a color attempt.
This matters for standards compliance because browsers may silently ignore an invalid box-shadow declaration entirely, meaning your intended shadow effect won’t render. Using valid CSS ensures consistent behavior across browsers and passes validation checks.
Recognized CSS color formats
The following formats are valid for the color component:
- Named colors: red, blue, transparent, currentcolor, etc.
- Hex codes: #000, #0000, #000000, #00000080
- RGB/RGBA: rgb(0, 0, 0), rgba(0, 0, 0, 0.5), or the modern rgb(0 0 0 / 50%)
- HSL/HSLA: hsl(0, 0%, 0%), hsla(0, 0%, 0%, 0.5), or hsl(0 0% 0% / 50%)
Examples
Incorrect — misspelled or invalid color values
<!-- "balck" is not a valid color name -->
<div style="box-shadow: 2px 4px 8px balck;">Typo in color</div>
<!-- "banana" is not a recognized CSS color -->
<div style="box-shadow: 2px 4px 8px banana;">Invalid color name</div>
<!-- "#12345" is not a valid hex code (5 digits) -->
<div style="box-shadow: 2px 4px 8px #12345;">Bad hex code</div>
Correct — valid color values
<!-- Named color -->
<div style="box-shadow: 2px 4px 8px black;">Named color</div>
<!-- Hex color -->
<div style="box-shadow: 2px 4px 8px #333;">Hex color</div>
<!-- RGBA for semi-transparency -->
<div style="box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.3);">Semi-transparent</div>
<!-- HSL color -->
<div style="box-shadow: 2px 4px 8px hsl(210, 50%, 40%);">HSL color</div>
Correct — omitting the color entirely
If you want the shadow to inherit the element’s text color, you can omit the color value altogether. This is valid and avoids the error:
<!-- Defaults to currentcolor -->
<div style="box-shadow: 2px 4px 8px;">Uses currentcolor</div>
Correct — multiple shadows with valid colors
<div style="box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2), inset 0 0 6px #ccc;">
Multiple shadows
</div>
To resolve this validation error, check the exact value the validator flags (the “X” in the error message), verify its spelling against the list of CSS named colors, and ensure any hex or function-based color uses correct syntax. If the color isn’t needed, simply remove it and let the browser default to currentcolor.
Unicode allows some characters to be represented in multiple ways. For example, the accented letter “é” can be stored as a single precomposed character (U+00E9) or as two separate code points: the base letter “e” (U+0065) followed by a combining acute accent (U+0301). While these look identical when rendered, they are fundamentally different at the byte level. Unicode Normalization Form C (NFC) is the canonical form that prefers the single precomposed representation whenever one exists.
The HTML specification and the W3C Character Model for the World Wide Web require that all text in HTML documents be in NFC. This matters for several reasons:
- String matching and search: Non-NFC text can cause failures when browsers or scripts try to match strings, compare attribute values, or process CSS selectors. Two visually identical strings in different normalization forms won’t match with simple byte comparison.
- Accessibility: Screen readers and assistive technologies may behave inconsistently when encountering decomposed character sequences.
- Interoperability: Different browsers, search engines, and tools may handle non-NFC text differently, leading to unpredictable behavior.
- Fragment identifiers and IDs: If an id attribute contains non-NFC characters, fragment links (#id) may fail to work correctly.
This issue most commonly appears when text is copied from word processors, PDFs, or other applications that use decomposed Unicode forms (NFD), or when content is generated by software that doesn’t normalize its output.
How to Fix It
- Identify the affected text: The validator will point to the specific line containing non-NFC characters. The characters will often look normal visually, so you’ll need to inspect them at the code-point level.
- Convert to NFC: Use a text editor or command-line tool that supports Unicode normalization. Many programming languages provide built-in normalization functions.
- Prevent future issues: Configure your text editor or build pipeline to save files in NFC. When accepting user input, normalize it server-side before storing or embedding in HTML.
In Python, you can normalize a string:
import unicodedata
normalized = unicodedata.normalize('NFC', original_string)
In JavaScript (Node.js or browser):
const normalized = originalString.normalize('NFC');
On the command line (using uconv from ICU):
uconv -x NFC input.html > output.html
Examples
Incorrect (decomposed form — NFD)
In this example, the letter “é” is represented as two code points (e + combining acute accent), which triggers the validation warning. The source may look identical to the correct version, but the underlying bytes differ:
<!-- "é" here is stored as U+0065 U+0301 (decomposed) -->
<p>Résumé available upon request.</p>
Correct (precomposed form — NFC)
Here, the same text uses the single precomposed character é (U+00E9):
<!-- "é" here is stored as U+00E9 (precomposed) -->
<p>Résumé available upon request.</p>
Incorrect in attributes
Non-NFC text in attribute values also triggers this issue:
<!-- The id contains a decomposed character -->
<h2 id="resumé">Résumé</h2>
Correct in attributes
<!-- The id uses the precomposed NFC character -->
<h2 id="resumé">Résumé</h2>
While these examples look the same in rendered text, the difference is in how the characters are encoded. To verify your text is in NFC, you can paste it into a Unicode inspector tool or use the normalization functions mentioned above. For further reading, the W3C provides an excellent guide on Normalization in HTML and CSS.
The CSS font shorthand property has a specific syntax defined in the CSS specification. At minimum, it requires both a font-size and a font-family value. Optionally, you can prepend font-style, font-variant, and font-weight, and you can append a line-height value after the font-size using a slash separator. The full syntax looks like this:
font: [font-style] [font-variant] [font-weight] font-size[/line-height] font-family;
When the W3C validator reports that a value “is not a font-family value,” it means the parser reached a point in the font declaration where it expected to find a font family name but instead found something it couldn’t interpret as one. This often happens in two scenarios:
- Using font when you meant a specific property — For example, writing font: 300 when you only intended to set the font weight. The validator tries to parse 300 as a complete font value, and since there’s no font-size or font-family, it fails.
- Incomplete font shorthand — Providing some values but forgetting the mandatory font-family at the end, such as font: 300 16px without a family name.
This matters because browsers may ignore an invalid font declaration entirely, causing your text to render with default or inherited styles instead of what you intended. Keeping your CSS valid also ensures consistent behavior across different browsers and helps maintain clean, predictable stylesheets.
How to fix it:
- If you only need to set a single font-related property, use the specific property (font-weight, font-size, font-style, font-variant, or font-family) instead of the font shorthand.
- If you want to use the font shorthand, make sure you include at least font-size and font-family, and that all values appear in the correct order.
- Remember that the font shorthand resets any omitted font sub-properties to their initial values, so use it deliberately.
Examples
Incorrect: Using font to set only the weight
<p style="font: 300;">This text has an invalid font declaration.</p>
The validator reports that 300 is not a valid font-family value because the font shorthand expects at least a font-size and font-family.
Correct: Using font-weight directly
<p style="font-weight: 300;">This text has a light font weight.</p>
Incorrect: Missing font-family in the shorthand
<p style="font: italic 300 16px;">This is also invalid.</p>
Even though font-style, font-weight, and font-size are all present, the required font-family is missing.
Correct: Complete font shorthand
<p style="font: italic 300 16px/1.5 'Helvetica', sans-serif;">This is valid.</p>
This includes all components in the correct order: font-style, font-weight, font-size/line-height, and font-family.
Correct: Minimal valid font shorthand
<p style="font: 16px sans-serif;">Only size and family — the minimum required.</p>
Correct: Using individual properties instead of the shorthand
<p style="font-style: italic; font-weight: 300; font-size: 16px; line-height: 1.5; font-family: 'Helvetica', sans-serif;">
Each property set individually.
</p>
Using individual properties avoids the pitfalls of the shorthand and gives you explicit control without accidentally resetting other font sub-properties.
When the HTML parser encounters a < character inside an opening tag, it doesn’t treat it as the start of a new tag — instead, it tries to interpret it as an attribute name. Since < is not a valid attribute name, the W3C validator raises this error. The browser may still render the page, but the behavior is undefined and can vary across different browsers, potentially leading to broken markup or elements that don’t display correctly.
This issue most commonly occurs in a few scenarios:
- Accidental keystrokes — a stray < typed while editing attributes.
- Copy-paste artifacts — fragments of other tags getting pasted into the middle of an element.
- Misplaced angle brackets — attempting to nest or close tags incorrectly, such as adding < before /> in a self-closing tag.
- Template or code generation errors — dynamic HTML output that incorrectly injects < into attribute positions.
Because this is a syntax-level problem, it can cause cascading parse errors. The parser may misinterpret everything after the stray < until it finds a matching >, which can swallow subsequent elements or attributes and produce unexpected rendering results.
How to Fix It
- Open the file referenced by the validator error and go to the indicated line number.
- Look inside the opening tag of the flagged element for a < character that doesn’t belong.
- Remove the stray < character.
- If the < was meant to represent a literal less-than sign in an attribute value, replace it with the HTML entity <.
Examples
Stray < before the closing slash
<!-- ❌ Stray "<" before the self-closing slash -->
<img src="photo.jpg" alt="smiling cat" < />
<!-- ✅ Fixed: removed the stray "<" -->
<img src="photo.jpg" alt="smiling cat" />
Stray < between attributes
<!-- ❌ Accidental "<" between attributes -->
<a href="/about" < class="nav-link">About</a>
<!-- ✅ Fixed: removed the stray "<" -->
<a href="/about" class="nav-link">About</a>
Fragment of another tag pasted inside an element
<!-- ❌ Leftover "<span" pasted inside the div's opening tag -->
<div class="card" <span>
<p>Hello world</p>
</div>
<!-- ✅ Fixed: removed the pasted fragment -->
<div class="card">
<p>Hello world</p>
</div>
Literal < intended in an attribute value
If you actually need a less-than sign inside an attribute value — for example, in a title or data-* attribute — use the < entity instead of a raw <.
<!-- ❌ Raw "<" in an attribute value can cause parsing issues -->
<span title="x < 10">Threshold</span>
<!-- ✅ Fixed: use the HTML entity -->
<span title="x < 10">Threshold</span>
The stroke-width property controls the thickness of the outline (stroke) drawn around shapes and text, primarily used in SVG but also applicable to HTML elements via CSS. According to both the SVG specification and the CSS standard, stroke-width accepts only non-negative values — that is, zero or any positive number, optionally with a CSS length unit like px, em, or rem. A unitless number is also valid and is interpreted in the current coordinate system’s user units.
Negative values are logically meaningless for stroke width because you cannot draw an outline with negative thickness. Browsers will typically ignore or discard the invalid declaration, meaning the stroke may render with an unexpected default width or not at all. Beyond rendering issues, using invalid CSS values causes W3C validation errors, which can indicate broader quality problems in your code and may lead to unpredictable behavior across different browsers.
A common cause of this error is dynamic value generation — for example, a CSS calc() expression or a preprocessor variable that inadvertently produces a negative result. If your stroke width is computed, make sure to clamp the value so it never goes below 0.
How to fix it
- Replace negative values with 0 or a positive number. If you intended no visible stroke, use 0. If you wanted a visible stroke, use the appropriate positive thickness.
- Guard computed values. If the value comes from a calc() expression or CSS custom property, use max() to ensure the result is never negative — for example, stroke-width: max(0px, calc(10px - 15px)).
- Check inline styles and stylesheets. The error can appear in both inline style attributes and external/internal CSS. Search your codebase for any stroke-width declaration with a negative number.
Examples
❌ Invalid: negative stroke-width on an HTML element
<p style="stroke-width: -1">Some content</p>
This triggers the validator error because -1 is not an allowed value.
✅ Fixed: non-negative stroke-width
<p style="stroke-width: 0">Some content</p>
Using 0 removes the stroke entirely and is valid.
❌ Invalid: negative stroke-width on an SVG element
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="-3" fill="none"/>
</svg>
✅ Fixed: positive stroke-width on an SVG element
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="none"/>
</svg>
✅ Using max() to clamp a computed value
<div style="stroke-width: max(0px, calc(5px - 10px))">Some content</div>
Here, calc(5px - 10px) would produce -5px, but max(0px, ...) ensures the final value is 0px, keeping the CSS valid.
Unlike margin properties, which accept negative values to pull elements closer together or overlap them, all padding properties (padding-top, padding-right, padding-bottom, padding-left, and the padding shorthand) are defined in the CSS specification to only accept zero or positive lengths. This is because padding represents the space inside an element between its content and its border — a negative internal space is not a meaningful concept.
When you use a negative padding value, browsers will typically ignore the declaration entirely, meaning your layout may not look the way you intended. The W3C validator catches this to help you identify code that won’t behave consistently across browsers and doesn’t conform to the CSS specification.
If your goal is to reduce the space between elements, negative margin values are the correct tool. If you’re trying to shift content upward within a container, consider using position: relative with a negative top offset, or adjust the layout with other techniques like transform: translateY().
Examples
❌ Invalid: negative padding value
<div style="padding-top: -20px;">
This element has invalid negative padding.
</div>
The validator will report: CSS: “padding-top”: “-20” negative values are not allowed.
✅ Fixed: using zero or positive padding
<div style="padding-top: 0;">
This element has no top padding.
</div>
<div style="padding-top: 10px;">
This element has valid positive top padding.
</div>
✅ Alternative: using negative margin instead
If you need to reduce the space above an element, use a negative margin-top:
<div style="margin-top: -20px;">
This element is pulled upward with a negative margin.
</div>
❌ Invalid: negative values in the padding shorthand
The same rule applies to the padding shorthand property. Any negative component value is invalid:
<div style="padding: -10px 20px 15px 20px;">
Invalid shorthand padding.
</div>
✅ Fixed: all-positive shorthand values
<div style="padding: 0 20px 15px 20px;">
Valid shorthand padding with zero top padding.
</div>
✅ Alternative: using transform for visual offset
If you need to visually shift an element’s content upward without affecting layout flow, transform is a clean option:
<div style="transform: translateY(-20px);">
This element appears shifted upward.
</div>
An <input type="hidden"> element is inherently invisible to all users. It is not rendered on the page, it cannot receive focus, and browsers automatically exclude it from the accessibility tree. The aria-hidden attribute is designed to hide visible content from assistive technologies like screen readers, but applying it to an element that is already fully hidden serves no purpose.
The HTML specification explicitly forbids the use of aria-hidden on hidden inputs. This restriction exists because combining the two is semantically meaningless — you cannot “hide from assistive technologies” something that is already invisible to everyone. Validators flag this as an error to encourage clean, standards-compliant markup and to help developers avoid misunderstandings about how ARIA attributes interact with native HTML semantics.
This issue commonly arises when aria-hidden="true" is applied broadly to a group of elements (for example, via a script or template) without checking whether specific children already handle their own visibility. It can also happen when developers add ARIA attributes as a precaution, not realizing that the native behavior of type="hidden" already covers accessibility concerns.
To fix this, remove the aria-hidden attribute from any <input> element whose type is hidden. No replacement is needed — the browser already handles everything correctly.
Examples
Incorrect
Adding aria-hidden to a hidden input triggers a validation error:
<form action="/submit" method="post">
<input type="hidden" aria-hidden="true" name="month" value="10">
<input type="hidden" aria-hidden="true" name="csrf_token" value="abc123">
<button type="submit">Submit</button>
</form>
Correct
Remove the aria-hidden attribute entirely. The hidden inputs are already inaccessible to all users by default:
<form action="/submit" method="post">
<input type="hidden" name="month" value="10">
<input type="hidden" name="csrf_token" value="abc123">
<button type="submit">Submit</button>
</form>
When aria-hidden is appropriate
The aria-hidden attribute is intended for elements that are visually present but should be hidden from assistive technologies, such as decorative icons:
<button type="button">
<span aria-hidden="true">★</span>
Favorite
</button>
In this case, the decorative star is visible on screen but irrelevant to screen reader users, so aria-hidden="true" correctly prevents it from being announced. This is the proper use case — hiding visible content from the accessibility tree, not redundantly marking already-hidden elements.
The <meta name="description"> element provides a brief summary of a page’s content. According to the WHATWG HTML living standard, there must be no more than one <meta> element per document where the name attribute has the value "description". This is a conformance requirement — not just a best practice — meaning that including duplicates produces invalid HTML.
Why this matters
Standards compliance: The HTML specification explicitly states that certain metadata names, including "description", must be unique within a document. Violating this makes your HTML non-conforming.
Search engine behavior: Search engines like Google use the meta description to generate snippet text in search results. When multiple description meta tags are present, search engines must decide which one to use — or may ignore them entirely and pull text from the page body instead. This can result in a less relevant or less compelling snippet, potentially reducing click-through rates.
Maintainability: Duplicate meta descriptions often arise from template conflicts — for example, a CMS injecting one description while a theme or plugin adds another. Having duplicates makes it unclear which description is actually intended, creating confusion for developers maintaining the code.
Common causes
- A CMS or static site generator automatically inserts a <meta name="description"> tag, while the template or theme also hardcodes one.
- Multiple HTML partials or includes each contribute their own description meta tag to the <head>.
- Copy-paste errors when building or editing the <head> section.
How to fix it
- Search your HTML source for all instances of <meta name="description".
- Decide which description best represents the page’s content.
- Remove all duplicate instances, keeping only one.
- If your content comes from templates or includes, trace where each tag is generated and ensure only one source outputs the description.
Examples
❌ Invalid: duplicate description meta tags
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Us</title>
<meta name="description" content="Learn about our company and mission.">
<meta name="description" content="We are a team of passionate developers.">
</head>
<body>
<h1>About Us</h1>
<p>Welcome to our about page.</p>
</body>
</html>
The validator will report an error because two <meta> elements share name="description".
✅ Valid: single description meta tag
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Us</title>
<meta name="description" content="Learn about our company, mission, and the team of passionate developers behind it.">
</head>
<body>
<h1>About Us</h1>
<p>Welcome to our about page.</p>
</body>
</html>
Here, the two descriptions have been merged into a single, more comprehensive meta description. Alternatively, you could simply keep whichever original description was more accurate and discard the other.
❌ Invalid: duplicates from mixed sources (common template issue)
<head>
<title>Blog Post</title>
<!-- Injected by CMS -->
<meta name="description" content="Auto-generated summary of the blog post.">
<!-- Hardcoded in theme template -->
<meta name="description" content="A blog about web development tips and tricks.">
<meta name="author" content="Jane Smith">
</head>
✅ Valid: single source of truth
<head>
<title>Blog Post</title>
<!-- Injected by CMS (theme duplicate removed) -->
<meta name="description" content="Auto-generated summary of the blog post.">
<meta name="author" content="Jane Smith">
</head>
When fixing template-driven duplicates, decide which system should own the description — typically the CMS, since it can generate page-specific descriptions — and remove the hardcoded one from the theme.
Make sure your final <meta name="description"> content is meaningful, concise (typically 150–160 characters), and accurately reflects what visitors will find on the page.
The transform CSS property lets you rotate, scale, skew, or translate an element by modifying its coordinate space. The W3C validator raises this error when the value assigned to transform doesn’t conform to valid CSS syntax. This typically happens when:
- A transform function name is misspelled (e.g., rotateZ typed as rotatez in some contexts, or skew typed as skeew).
- Too many arguments are passed to a transform function (e.g., rotate(45deg, 20deg) instead of rotate(45deg)).
- Arguments are missing required units (e.g., rotate(45) instead of rotate(45deg)).
- Multiple transform functions are separated by commas instead of spaces.
- An invalid or non-existent function name is used (e.g., transform: flip()).
- Vendor-prefixed values like -webkit-transform syntax are used in the standard transform property incorrectly.
This matters for standards compliance because browsers may silently ignore an invalid transform declaration entirely, meaning none of your intended transformations will be applied. Catching these errors during validation helps prevent unexpected layout or visual issues.
Each transform function has a specific signature. For example, rotate() accepts exactly one angle value, translate() accepts one or two length/percentage values, and scale() accepts one or two numbers. Providing the wrong number or type of arguments triggers this error.
Examples
Incorrect: Comma-separated transform functions
Multiple transforms must be space-separated, not comma-separated.
<div style="transform: rotate(45deg), scale(1.5);">Transformed</div>
Correct: Space-separated transform functions
<div style="transform: rotate(45deg) scale(1.5);">Transformed</div>
Incorrect: Missing unit on rotation value
The rotate() function requires an angle unit such as deg, rad, grad, or turn.
<div style="transform: rotate(45);">Rotated</div>
Correct: Angle value with unit
<div style="transform: rotate(45deg);">Rotated</div>
Incorrect: Too many arguments in a function
The rotate() function accepts only one argument.
<div style="transform: rotate(45deg, 20deg);">Rotated</div>
Correct: Single argument for rotate()
If you need to rotate around a specific axis, use rotateX(), rotateY(), or rotateZ() instead.
<div style="transform: rotateZ(45deg);">Rotated on Z axis</div>
Incorrect: Misspelled or non-existent function
<div style="transform: roate(30deg) scaleX(2);">Transformed</div>
Correct: Properly spelled function names
<div style="transform: rotate(30deg) scaleX(2);">Transformed</div>
Incorrect: Using translate without units on non-zero lengths
<div style="transform: translate(50, 100);">Moved</div>
Correct: Length values with units
A value of 0 does not require a unit, but all other length values do.
<div style="transform: translate(50px, 100px);">Moved</div>
Valid Transform Functions Reference
Here are the commonly used transform functions and their expected arguments:
- translate(tx) or translate(tx, ty) — lengths or percentages
- translateX(tx), translateY(ty), translateZ(tz) — a single length/percentage
- scale(sx) or scale(sx, sy) — unitless numbers
- scaleX(sx), scaleY(sy), scaleZ(sz) — a single unitless number
- rotate(angle) — a single angle value (e.g., 45deg)
- rotateX(angle), rotateY(angle), rotateZ(angle) — a single angle
- skew(ax) or skew(ax, ay) — angle values
- skewX(ax), skewY(ay) — a single angle
- matrix(a, b, c, d, tx, ty) — exactly six unitless numbers
- matrix3d(...) — exactly sixteen unitless numbers
When combining multiple transforms, always separate them with spaces and verify each function’s name and argument count against the specification.
The controlslist attribute was proposed to give developers a way to hint to the browser which default media controls to show or hide. It accepts values like nodownload, nofullscreen, and noremoteplayback, allowing you to selectively disable specific buttons in the browser’s built-in media player UI. For example, controlslist="nodownload" hides the download button on the video player.
However, this attribute was never adopted into the WHATWG HTML Living Standard or any W3C specification. It remains a Chromium-specific feature, meaning it only works in browsers like Chrome and Edge. Firefox, Safari, and other non-Chromium browsers simply ignore it. Because it’s not part of any standard, the W3C HTML Validator rightfully reports it as an invalid attribute.
While using controlslist won’t break your page — browsers that don’t recognize it will silently ignore it — relying on non-standard attributes has downsides:
- Standards compliance: Your HTML won’t validate, which can mask other real issues in validation reports.
- Browser compatibility: The behavior only works in Chromium-based browsers, giving an inconsistent experience across browsers.
- Future uncertainty: Non-standard attributes can be removed or changed without notice.
To fix this, you have a few options. The simplest is to remove the attribute entirely if the customization isn’t critical. If you need fine-grained control over media player buttons, the most robust approach is to build custom media controls using JavaScript and the HTMLMediaElement API. For the specific case of disabling remote playback, there is a standardized attribute — disableremoteplayback — that you can use instead.
Examples
❌ Invalid: Using the non-standard controlslist attribute
<video src="video.mp4" controls controlslist="nodownload nofullscreen"></video>
The validator will report: Attribute “controlslist” not allowed on element “video” at this point.
✅ Valid: Removing the attribute
<video src="video.mp4" controls></video>
The simplest fix is to remove controlslist and accept the browser’s default controls.
✅ Valid: Using custom controls with JavaScript
<video id="my-video" src="video.mp4"></video>
<div class="custom-controls">
<button id="play-btn">Play</button>
<input id="seek-bar" type="range" min="0" max="100" value="0">
<button id="fullscreen-btn">Fullscreen</button>
</div>
<script>
const video = document.getElementById("my-video");
document.getElementById("play-btn").addEventListener("click", () => {
video.paused ? video.play() : video.pause();
});
</script>
By omitting the controls attribute and building your own UI, you have full control over which buttons appear — across all browsers.
✅ Valid: Using disableremoteplayback for that specific need
<video src="video.mp4" controls disableremoteplayback></video>
If your goal was specifically controlslist="noremoteplayback", the standardized disableremoteplayback attribute achieves the same result and is valid HTML.
Audio element
The same issue and solutions apply to the <audio> element:
<!-- ❌ Invalid -->
<audio src="song.mp3" controls controlslist="nodownload"></audio>
<!-- ✅ Valid -->
<audio src="song.mp3" controls></audio>
The margin shorthand property sets the margin area on all four sides of an element. It accepts one to four values, where each value must be a valid CSS length (e.g., 10px, 1em, 0), a percentage, or the keyword auto. When the validator reports “Too many values or values are not recognized,” it means either more than four values were supplied, or at least one of the values is something CSS doesn’t understand — such as a misspelled unit, a missing unit on a non-zero number, or an invalid keyword.
Common causes of this error include:
- Too many values: Providing five or more values (e.g., margin: 1px 2px 3px 4px 5px). The shorthand accepts a maximum of four.
- Missing units: Writing a non-zero number without a unit (e.g., margin: 10 instead of margin: 10px). Only 0 is valid without a unit.
- Typos or invalid units: Using a misspelled or nonexistent unit like margin: 10xp or margin: 10pixels.
- Invalid keywords: Using a keyword that isn’t recognized in the margin context (e.g., margin: none). The only non-global keyword margin accepts is auto.
- Missing separators or extra characters: Including commas or other unexpected characters between values (e.g., margin: 10px, 20px). Values should be separated by spaces, not commas.
This matters because browsers may ignore or misinterpret an invalid margin declaration entirely, leading to broken or inconsistent layouts across different browsers. Writing valid CSS ensures predictable rendering and easier maintenance.
How margin shorthand values work
The number of values you provide determines how they are applied:
- 1 value: Applied to all four sides. margin: 10px → top, right, bottom, and left all get 10px.
- 2 values: First is top and bottom, second is left and right. margin: 10px 20px → top/bottom 10px, left/right 20px.
- 3 values: First is top, second is left and right, third is bottom. margin: 10px 20px 30px.
- 4 values: Applied clockwise — top, right, bottom, left. margin: 10px 20px 30px 40px.
Examples
❌ Too many values
/* Five values — invalid */
.box {
margin: 10px 20px 30px 40px 50px;
}
❌ Missing unit on a non-zero number
.box {
margin: 10 20px;
}
❌ Invalid keyword
.box {
margin: none;
}
❌ Comma-separated values
.box {
margin: 10px, 20px;
}
✅ Correct: one to four valid values
/* All four sides */
.box {
margin: 10px;
}
/* Top/bottom and left/right */
.box {
margin: 10px 20px;
}
/* Top, left/right, bottom */
.box {
margin: 10px auto 20px;
}
/* Top, right, bottom, left */
.box {
margin: 10px 20px 30px 40px;
}
✅ Correct: using auto for centering
.container {
margin: 0 auto;
}
✅ Correct: zero without a unit
.box {
margin: 0;
}
✅ Correct: using global keywords
.box {
margin: inherit;
}
If you need to set margins on more than four sides independently (which isn’t possible — elements only have four sides), you likely have a logic error. If you want fine-grained control, use the individual longhand properties (margin-top, margin-right, margin-bottom, margin-left) instead of the shorthand.
Ready to validate your sites?
Start your free trial today.