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.
An img element without an alt attribute cannot carry any aria-* attributes except aria-hidden.
When an img lacks an alt attribute, its role and purpose are undefined from an accessibility standpoint. The HTML specification treats this as an image whose text alternative has not been provided, and assistive technologies handle it in a special fallback way. In this state, adding ARIA attributes like aria-label, aria-labelledby, or aria-describedby creates a contradiction: the markup simultaneously says "this image has no known alternative" and "here is semantic information about this image."
The only ARIA attribute allowed on an img without alt is aria-hidden="true", because hiding the image from the accessibility tree is consistent with having no text alternative.
There are two ways to fix this. Either add an alt attribute to the img (which you should almost always do), or remove the ARIA attributes and optionally add aria-hidden="true" if the image is purely decorative.
HTML examples
Invalid: img with no alt but with aria-label
<imgsrc="photo.jpg"aria-label="A sunset over the ocean">
Fixed: add an alt attribute
If the image conveys meaning, provide an alt attribute. Once alt is present, ARIA attributes are allowed.
<imgsrc="photo.jpg"alt="A sunset over the ocean">
Fixed: decorative image hidden from assistive technology
If the image is decorative and carries no meaning, use an empty alt or drop ARIA attributes in favor of aria-hidden:
<imgsrc="decoration.jpg"alt="">
<imgsrc="decoration.jpg"aria-hidden="true">
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-invalidonto 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
<formaction="/submit"method="post">
<inputtype="hidden"name="referer"value="https://example.com"aria-invalid="false">
<buttontype="submit">Submit</button>
</form>
Correct: hidden input without aria-* attributes
<formaction="/submit"method="post">
<inputtype="hidden"name="referer"value="https://example.com">
<buttontype="submit">Submit</button>
</form>
Incorrect: hidden input with multiple aria-* attributes
<formaction="/save"method="post">
<input
type="hidden"
name="session_token"
value="abc123"
aria-label="Session token"
aria-required="true"
aria-describedby="token-help">
<buttontype="submit">Save</button>
</form>
Correct: all aria-* attributes removed
<formaction="/save"method="post">
<inputtype="hidden"name="session_token"value="abc123">
<buttontype="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":
<formaction="/login"method="post">
<labelfor="username">Username</label>
<input
type="text"
id="username"
name="username"
aria-required="true"
aria-invalid="false"
aria-describedby="username-help">
<pid="username-help">Enter your registered email or username.</p>
<buttontype="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>
ulli{
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.
The CSS fill property does not accept linear-gradient() as a valid value.
The fill property is used primarily with SVG elements to define the color that fills the interior of a shape. It accepts simple color values like named colors, hex codes, rgb(), hsl(), currentColor, none, or a reference to an SVG paint server using url().
CSS gradients such as linear-gradient() are classified as <image> values, not <color> values, so they cannot be used directly with fill. This is different from properties like background or background-image, which do accept gradient values.
If you need a gradient fill on an SVG element, you should define a <linearGradient> inside an SVG <defs> block and reference it with url().
How to fix it
Invalid: Using linear-gradient() with fill:
<svgwidth="100"height="100">
<rectwidth="100"height="100"style="fill:linear-gradient(to right, red, blue);"/>
</svg>
Valid: Using an SVG <linearGradient> definition:
<svgwidth="100"height="100">
<defs>
<linearGradientid="myGradient"x1="0"y1="0"x2="1"y2="0">
<stopoffset="0%"stop-color="red"/>
<stopoffset="100%"stop-color="blue"/>
</linearGradient>
</defs>
<rectwidth="100"height="100"fill="url(#myGradient)"/>
</svg>
The x1, y1, x2, and y2 attributes on <linearGradient> control the direction of the gradient. In this example, x1="0" x2="1" creates a left-to-right gradient, equivalent to to right in CSS.
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:
<optionvalue="1">Option 1</option>
<optionvalue="2">Option 2</option>
</select>
Correct: move instructions to a label
<labelfor="flavor">Please select a flavor:</label>
<selectid="flavor"name="flavor">
<optionvalue="vanilla">Vanilla</option>
<optionvalue="chocolate">Chocolate</option>
</select>
Correct: provide a non-selectable prompt inside select
<labelfor="country">Country</label>
<selectid="country"name="country"required>
<optionvalue=""disabledselectedhidden>Select a country</option>
<optionvalue="us">United States</option>
<optionvalue="ca">Canada</option>
</select>
Correct: use optgroup for grouping, not free text
<labelfor="city">City</label>
<selectid="city"name="city">
<optgrouplabel="USA">
<optionvalue="nyc">New York</option>
<optionvalue="la">Los Angeles</option>
</optgroup>
<optgrouplabel="Canada">
<optionvalue="toronto">Toronto</option>
<optionvalue="vancouver">Vancouver</option>
</optgroup>
</select>
Correct: full document (for context)
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Select without stray text</title>
</head>
<body>
<form>
<labelfor="size">Choose a size:</label>
<selectid="size"name="size">
<optionvalue=""disabledselectedhidden>Select a size</option>
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
</form>
</body>
</html>
Tips:
- Put instructions in a
labelor surrounding text, not insideselect. - Every choice must be an
option; useoptgroupwithlabelto 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()andrgba()need proper comma-separated or space-separated values with closing parentheses. - Use
background-colorinstead of the shorthandbackgroundif 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
<divstyle="background: aquaa;">Content</div>
aquaa is not a valid CSS color name, so the validator rejects it.
Correct — Valid color name
<divstyle="background: aqua;">Content</div>
Incorrect — Malformed hex code
<divstyle="background:#xyz123;">Content</div>
Hex color codes only allow characters 0–9 and a–f.
Correct — Valid hex code
<divstyle="background:#00a123;">Content</div>
Incorrect — Missing hash symbol
<divstyle="background: ff0000;">Content</div>
Without the #, the validator interprets ff0000 as an unknown keyword.
Correct — Hex code with hash
<divstyle="background:#ff0000;">Content</div>
Incorrect — Broken rgb() syntax
<divstyle="background:rgb(255,0,300);">Content</div>
RGB channel values must be between 0 and 255 (or 0% to 100%).
Correct — Valid rgb() value
<divstyle="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:
<divstyle="background-color:rgba(255,0,0,0.5);">Semi-transparent red</div>
Correct — Valid shorthand with image and other properties
<divstyle="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
0or a positive number. If you simply want no padding, use0. If you need some spacing, use a positive value. - Use
margin-rightfor 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(), Flexboxgap, 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>
<divclass="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>
<divclass="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>
<divclass="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>
<divclass="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:
srcattribute — Provide a URL pointing to the document you want to embed.srcdocattribute — 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
<iframesrc="https://example.com"title="Example website"></iframe>
This loads the document at the specified URL inside the iframe.
✅ Valid: using the srcdoc attribute
<iframesrcdoc="<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)
<iframesrc="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:
<iframesrc="/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.
An invalid value was passed to the drop-shadow() function inside the CSS filter property.
The drop-shadow() function accepts a shadow definition similar to box-shadow, but with some restrictions. It takes two or three length values (offset-x, offset-y, and an optional blur-radius), plus an optional color. Unlike box-shadow, it does not accept a spread-radius value or the inset keyword.
The correct syntax is:
filter: drop-shadow(offset-x offset-y blur-radius color);
Each parameter has specific requirements:
offset-xandoffset-yare required length values (e.g.,2px,0.5em).blur-radiusis optional and must be a non-negative length. Negative values are not allowed.coloris optional. If omitted, the browser uses the element'scolorproperty value.
Common mistakes that trigger this error include adding a spread-radius (a fourth length value), using the inset keyword, or passing a malformed color value.
Examples
This is invalid because drop-shadow() does not support a spread-radius (the 5px fourth length value):
<divstyle="filter:drop-shadow(4px4px10px5px red);">
Shadow content
</div>
Remove the spread-radius to fix it:
<divstyle="filter:drop-shadow(4px4px10px red);">
Shadow content
</div>
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.,
greyyinstead ofgrey, orbalckinstead ofblack. - Invalid hex codes — e.g.,
#GGGor#12345(hex codes must be 3, 4, 6, or 8 hex digits). - Fabricated color names — e.g.,
bananaordarkwhite, 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 ofrgba(), 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 modernrgb(0 0 0 / 50%) - HSL/HSLA:
hsl(0, 0%, 0%),hsla(0, 0%, 0%, 0.5), orhsl(0 0% 0% / 50%)
Examples
Incorrect — misspelled or invalid color values
<!-- "balck" is not a valid color name -->
<divstyle="box-shadow:2px4px8px balck;">Typo in color</div>
<!-- "banana" is not a recognized CSS color -->
<divstyle="box-shadow:2px4px8px banana;">Invalid color name</div>
<!-- "#12345" is not a valid hex code (5 digits) -->
<divstyle="box-shadow:2px4px8px#12345;">Bad hex code</div>
Correct — valid color values
<!-- Named color -->
<divstyle="box-shadow:2px4px8px black;">Named color</div>
<!-- Hex color -->
<divstyle="box-shadow:2px4px8px#333;">Hex color</div>
<!-- RGBA for semi-transparency -->
<divstyle="box-shadow:2px4px8pxrgba(0,0,0,0.3);">Semi-transparent</div>
<!-- HSL color -->
<divstyle="box-shadow:2px4px8pxhsl(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 -->
<divstyle="box-shadow:2px4px8px;">Uses currentcolor</div>
Correct — multiple shadows with valid colors
<divstyle="box-shadow:2px2px4pxrgba(0,0,0,0.2), inset 006px#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
idattribute 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:
importunicodedata
normalized=unicodedata.normalize('NFC', original_string)
In JavaScript (Node.js or browser):
constnormalized=originalString.normalize('NFC');
On the command line (using uconv from ICU):
uconv-xNFCinput.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 -->
<h2id="resumé">Résumé</h2>
Correct in attributes
<!-- The id uses the precomposed NFC character -->
<h2id="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.
A CSS color value (like a hex code or color name) was used where a background-image value is expected, or vice versa — the background-image property only accepts image functions such as url() or gradient functions, not plain color values.
The background-image property is specifically designed for setting images or gradients as backgrounds. If you want to set a solid background color, use the background-color property instead. Alternatively, you can use the shorthand background property, which accepts both colors and images.
This error often occurs when using the background shorthand incorrectly or when accidentally assigning a color value directly to background-image in inline styles.
Incorrect Example
<divstyle="background-image:#ff0000;">
This will trigger a validation error.
</div>
Correct Examples
Use background-color for solid colors:
<divstyle="background-color:#ff0000;">
Using background-color for a solid color.
</div>
Use background-image only for images or gradients:
<divstyle="background-image:url('banner.jpg');">
Using background-image with a URL.
</div>
<divstyle="background-image:linear-gradient(to right,#ff0000,#0000ff);">
Using background-image with a gradient.
</div>
Or use the background shorthand, which accepts both:
<divstyle="background:#ff0000url('banner.jpg') no-repeat center;">
Using the background shorthand.
</div>
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
fontwhen you meant a specific property — For example, writingfont: 300when you only intended to set the font weight. The validator tries to parse300as a completefontvalue, and since there's nofont-sizeorfont-family, it fails. - Incomplete
fontshorthand — Providing some values but forgetting the mandatoryfont-familyat the end, such asfont: 300 16pxwithout 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, orfont-family) instead of thefontshorthand. - If you want to use the
fontshorthand, make sure you include at leastfont-sizeandfont-family, and that all values appear in the correct order. - Remember that the
fontshorthand resets any omitted font sub-properties to their initial values, so use it deliberately.
Examples
Incorrect: Using font to set only the weight
<pstyle="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
<pstyle="font-weight:300;">This text has a light font weight.</p>
Incorrect: Missing font-family in the shorthand
<pstyle="font: italic 30016px;">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
<pstyle="font: italic 30016px/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
<pstyle="font:16px sans-serif;">Only size and family — the minimum required.</p>
Correct: Using individual properties instead of the shorthand
<pstyle="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.
Negative values for the CSS line-height property are invalid and browsers reject them.
The line-height property sets the height of each line box, so a negative value has no meaning and violates the CSS specification. Valid values are normal, a unitless multiplier like 1.5, a length like 24px or 1.5em, or a percentage like 150%. A unitless number is usually the best choice, since it scales with the element's font size and avoids the inheritance quirks that fixed lengths can cause.
This error usually appears when line-height is set inline through the style attribute, where the W3C validator catches it during HTML validation. A common cause is a calculation in a template or script that produces a negative number and writes it into the markup.
To fix it, replace the negative value with a positive one, or use normal for the browser's default spacing. Tighter lines are fine as long as the value stays positive, such as line-height: 0.9.
HTML examples
Invalid: negative line-height value
<pstyle="line-height:-0.5em;">
Paragraph text
</p>
Valid: positive value or normal
<!-- Unitless multiplier, scales with font size -->
<pstyle="line-height:1.5;">
Paragraph text
</p>
<!-- Browser default spacing -->
<pstyle="line-height: normal;">
Paragraph text
</p>
The role attribute is not allowed on a label element when that label is associated with a form control (a labelable element) through the for attribute or by nesting.
When a label is associated with a form control, the browser already understands its purpose — it's a label. Adding a role attribute overrides this native semantics, which is redundant at best and confusing for assistive technologies at worst.
A label becomes "associated" with a labelable element in two ways: explicitly via the for attribute pointing to the control's id, or implicitly by wrapping the control inside the label. Labelable elements include input (except type="hidden"), select, textarea, button, meter, output, and progress.
If the label is associated, simply remove the role attribute. The native semantics are already correct and sufficient.
If you truly need a custom role for some reason and the label is not functionally labeling a control, you can disassociate it by removing the for attribute or unnesting the control — but this is rarely the right approach.
Invalid Example
<labelfor="email"role="presentation">Email</label>
<inputtype="email"id="email">
Valid Example
<labelfor="email">Email</label>
<inputtype="email"id="email">
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 -->
<imgsrc="photo.jpg"alt="smiling cat"</>
<!-- ✅ Fixed: removed the stray "<" -->
<imgsrc="photo.jpg"alt="smiling cat"/>
Stray < between attributes
<!-- ❌ Accidental "<" between attributes -->
<ahref="/about"<class="nav-link">About</a>
<!-- ✅ Fixed: removed the stray "<" -->
<ahref="/about"class="nav-link">About</a>
Fragment of another tag pasted inside an element
<!-- ❌ Leftover "<span" pasted inside the div's opening tag -->
<divclass="card"<span>
<p>Hello world</p>
</div>
<!-- ✅ Fixed: removed the pasted fragment -->
<divclass="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 -->
<spantitle="x < 10">Threshold</span>
<!-- ✅ Fixed: use the HTML entity -->
<spantitle="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
0or a positive number. If you intended no visible stroke, use0. 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, usemax()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
styleattributes and external/internal CSS. Search your codebase for anystroke-widthdeclaration with a negative number.
Examples
❌ Invalid: negative stroke-width on an HTML element
<pstyle="stroke-width: -1">Some content</p>
This triggers the validator error because -1 is not an allowed value.
✅ Fixed: non-negative stroke-width
<pstyle="stroke-width: 0">Some content</p>
Using 0 removes the stroke entirely and is valid.
❌ Invalid: negative stroke-width on an SVG element
<svgwidth="100"height="100"xmlns="http://www.w3.org/2000/svg">
<circlecx="50"cy="50"r="40"stroke="black"stroke-width="-3"fill="none"/>
</svg>
✅ Fixed: positive stroke-width on an SVG element
<svgwidth="100"height="100"xmlns="http://www.w3.org/2000/svg">
<circlecx="50"cy="50"r="40"stroke="black"stroke-width="3"fill="none"/>
</svg>
✅ Using max() to clamp a computed value
<divstyle="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.
Text content cannot be placed directly inside an ol element. All text within an ordered list must be wrapped in li elements.
The ol element accepts only li, script, and template elements as children. Any bare text node placed directly between <ol> and </ol> (or between </li> and <li>) is invalid. This includes whitespace-only text nodes in some edge cases, but the validator typically flags visible text content.
This error often appears when list items are missing their <li> tags, or when extra text (like a heading or description) is placed inside the ol instead of before or after it.
Invalid example
<ol>
Some introductory text
<li>First item</li>
<li>Second item</li>
</ol>
Valid example
Move the text outside the ol, or wrap each piece of text in an li element:
<p>Some introductory text</p>
<ol>
<li>First item</li>
<li>Second item</li>
</ol>
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
<divstyle="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
<divstyle="padding-top:0;">
This element has no top padding.
</div>
<divstyle="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:
<divstyle="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:
<divstyle="padding:-10px20px15px20px;">
Invalid shorthand padding.
</div>
✅ Fixed: all-positive shorthand values
<divstyle="padding:020px15px20px;">
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:
<divstyle="transform:translateY(-20px);">
This element appears shifted upward.
</div>
An img element with a role attribute cannot have an empty alt attribute because the role overrides the default semantics, and an empty alt signals that the image is decorative — two intentions that contradict each other.
When an img has alt="", it tells assistive technologies to skip the image entirely. The image is treated as presentational, equivalent to role="presentation" or role="none". Assigning a different role (such as button, link, or tab) tells assistive technologies the element has a specific function and should be announced. These two signals conflict: one says "ignore this," the other says "this is interactive" or "this has meaning."
To fix this, decide what the image actually does:
- If the image is decorative and should be ignored, remove the
roleattribute and keepalt="". - If the image has a functional role, provide a descriptive
altvalue that explains the image's purpose.
HTML examples
Image with conflicting role and empty alt
<imgsrc="search.png"alt=""role="button">
Fixed: decorative image without a role
If the image is purely decorative, remove the role:
<imgsrc="search.png"alt="">
Fixed: functional image with descriptive alt
If the image acts as a button, give it a meaningful alt:
<imgsrc="search.png"alt="Search"role="button">
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:
<formaction="/submit"method="post">
<inputtype="hidden"aria-hidden="true"name="month"value="10">
<inputtype="hidden"aria-hidden="true"name="csrf_token"value="abc123">
<buttontype="submit">Submit</button>
</form>
Correct
Remove the aria-hidden attribute entirely. The hidden inputs are already inaccessible to all users by default:
<formaction="/submit"method="post">
<inputtype="hidden"name="month"value="10">
<inputtype="hidden"name="csrf_token"value="abc123">
<buttontype="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:
<buttontype="button">
<spanaria-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 aria-label attribute is not allowed on a label element that is associated with a form control through the for attribute.
A <label> element already provides an accessible name for the form control it's associated with. Adding aria-label to the <label> itself creates a conflict: the aria-label would attempt to override the label's own accessible name, but the label's visible text is what gets passed to the associated form control. This redundancy is not only unnecessary but explicitly prohibited by the HTML specification.
The <label> element's purpose is to be the accessible label for another element. If you want the form control to have an accessible name, simply put that text inside the <label> element as visible content. If you need to provide a different accessible name directly to the form control, place the aria-label on the input element instead.
Incorrect Example
<labelfor="input_email"id="label_input_email"aria-label="Email">
</label>
<inputtype="email"id="input_email">
Correct Example
The simplest fix is to remove the aria-label from the <label>, since the label's text content already serves as the accessible name for the input:
<labelfor="input_email"id="label_input_email">
</label>
<inputtype="email"id="input_email">
If you need the accessible name to differ from the visible label text, place aria-label on the input instead:
<labelfor="input_email"id="label_input_email">
</label>
<inputtype="email"id="input_email"aria-label="Your email address">
An aria-hidden attribute on a <label> element that is associated with a form control hides the label from assistive technologies while the control still expects an accessible name from it. This creates a broken association where the form field appears unlabeled to screen readers.
The aria-hidden="true" attribute removes an element and all its descendants from the accessibility tree. When a <label> is linked to an input (either by wrapping it or through the for attribute), the browser uses that label to compute the input's accessible name. Setting aria-hidden="true" on the label strips away that name, leaving the input without a label for assistive technology users.
The HTML spec explicitly forbids this combination because it guarantees an accessibility failure. A label that exists visually but is hidden from the accessibility tree defeats its own purpose.
To fix this, remove aria-hidden from the <label>. If the label text should not be visible on screen, use a CSS technique to visually hide it while keeping it accessible. If the label itself is truly decorative and another mechanism provides the accessible name (like aria-label on the input), remove the for/id association so the <label> is no longer linked to the control.
Incorrect example
<labelfor="email"aria-hidden="true">Email</label>
<inputtype="email"id="email">
Correct examples
Remove aria-hidden from the label:
<labelfor="email">Email</label>
<inputtype="email"id="email">
If the goal is to visually hide the label while keeping it accessible, use CSS instead:
<labelfor="email"class="visually-hidden">Email</label>
<inputtype="email"id="email">
<style>
.visually-hidden{
clip:rect(0000);
clip-path:inset(50%);
height:1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width:1px;
}
</style>
If the label is decorative and the input gets its accessible name from another source, break the association:
<labelaria-hidden="true">Email</label>
<inputtype="email"aria-label="Email">
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>
<htmllang="en">
<head>
<title>About Us</title>
<metaname="description"content="Learn about our company and mission.">
<metaname="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>
<htmllang="en">
<head>
<title>About Us</title>
<metaname="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 -->
<metaname="description"content="Auto-generated summary of the blog post.">
<!-- Hardcoded in theme template -->
<metaname="description"content="A blog about web development tips and tricks.">
<metaname="author"content="Jane Smith">
</head>
✅ Valid: single source of truth
<head>
<title>Blog Post</title>
<!-- Injected by CMS (theme duplicate removed) -->
<metaname="description"content="Auto-generated summary of the blog post.">
<metaname="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 aria-checked attribute is not allowed on a label element when that label is associated with a form control like a checkbox or radio button.
When a label is associated with a labelable element (via the for attribute or by nesting), the label acts as an accessible name provider — it doesn't represent the control's state itself. The aria-checked attribute is meant for elements that act as a checkbox or radio button, such as elements with role="checkbox" or role="switch", not for labels that merely describe one.
Adding aria-checked to a label creates conflicting semantics. Assistive technologies already read the checked state from the associated input element, so duplicating or overriding that state on the label causes confusion.
If you need a custom toggle or checkbox, apply aria-checked to the element that has the interactive role, not to the label.
Example with the issue
<labelfor="notifications"aria-checked="true">Enable notifications</label>
<inputtype="checkbox"id="notifications"checked>
Fixed example using a native checkbox
<labelfor="notifications">Enable notifications</label>
<inputtype="checkbox"id="notifications"checked>
The native <input type="checkbox"> already communicates its checked state to assistive technologies — no aria-checked is needed anywhere.
Fixed example using a custom toggle
If you're building a custom control without a native checkbox, apply aria-checked to the element with the appropriate role:
<spanid="toggle-label">Enable notifications</span>
<spanrole="switch"tabindex="0"aria-checked="true"aria-labelledby="toggle-label"></span>
Here, aria-checked is correctly placed on the element with role="switch", which is the interactive control. The label is a separate <span> referenced via aria-labelledby, keeping roles and states cleanly separated.
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