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 CSS perspective property does not accept 0 as a valid value. It must be either none or a positive length greater than zero.
The perspective property defines the distance between the viewer and the z=0 plane, which determines the intensity of 3D effects on child elements. A value of 0 is mathematically undefined (it would place the viewer directly on the plane), so the specification rejects it. If the goal is to remove perspective entirely, use none. If the goal is a very strong perspective effect, use a small positive value like 1px.
The property applies to the element's children, not the element itself. A smaller value creates a more dramatic 3D effect, while a larger value creates a subtler one.
Invalid example
<div style="perspective: 0;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
Valid examples
To disable perspective:
<div style="perspective: none;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
To apply a strong (but valid) perspective:
<div style="perspective: 1px;">
<div style="transform: rotateY(45deg);">Content</div>
</div>
The pointer-events CSS property controls whether an element can be the target of pointer events such as clicks, taps, and hover states. Unlike many other CSS properties that accept normal as a keyword (e.g., white-space, letter-spacing), the pointer-events property does not include normal in its list of valid values. This is a common mistake since normal and auto are often used interchangeably across different CSS properties, but each property defines its own set of accepted keywords.
Using an invalid value like normal means the browser will ignore the entire declaration. In most cases this won't cause visible breakage because the default behavior already allows pointer interaction, but it can lead to unexpected results if you're relying on the property to override an inherited pointer-events: none from a parent element. The invalid declaration would simply be discarded, and the inherited none value would remain in effect, making the element unclickable.
For standard HTML elements, the two primary values you'll use are:
auto— The element behaves as it normally would regarding pointer events. This is the default.none— The element is never the target of pointer events. Clicks and hovers pass through to whatever is behind it.
Several additional values exist (visiblePainted, visibleFill, visibleStroke, visible, painted, fill, stroke, all) but these only apply to SVG elements. Global CSS keywords like inherit, initial, unset, and revert are also valid.
To fix the issue, replace normal with auto wherever it appears as a pointer-events value. If you're using pointer-events: normal to restore default interactivity after a parent set pointer-events: none, then auto is exactly what you need.
Examples
Incorrect — using normal
<div style="pointer-events: normal;">
<a href="/about">About us</a>
</div>
The validator will flag normal as an invalid value for pointer-events.
Correct — using auto
<div style="pointer-events: auto;">
<a href="/about">About us</a>
</div>
Practical use case: restoring pointer events on a child
A common pattern is disabling pointer events on a parent and re-enabling them on a specific child. Using normal here would silently fail, leaving the button unclickable:
<!-- Incorrect -->
<style>
.overlay {
pointer-events: none;
}
.overlay .close-btn {
pointer-events: normal; /* Invalid — button remains unclickable */
}
</style>
<div class="overlay">
<button class="close-btn">Close</button>
</div>
<!-- Correct -->
<style>
.overlay {
pointer-events: none;
}
.overlay .close-btn {
pointer-events: auto; /* Valid — button is clickable again */
}
</style>
<div class="overlay">
<button class="close-btn">Close</button>
</div>
In the incorrect version, the browser discards the invalid pointer-events: normal declaration entirely, so the .close-btn inherits none from the parent and cannot be clicked. Changing it to auto correctly restores interactivity on the button.
In CSS, a length value is composed of two parts: a number and a unit. Writing just px provides the unit but omits the number, which makes the declaration invalid. The CSS parser cannot interpret px alone as a meaningful measurement, so the property is ignored entirely. This means your intended layout won't be applied, potentially causing elements to render at unexpected sizes across different browsers.
This error commonly arises from typos, copy-paste mistakes, or templating issues where a variable that should output a number resolves to an empty string, leaving behind only the unit suffix. It can also happen when a numeric value is accidentally deleted during editing.
Beyond layout breakdowns, invalid CSS can cause inconsistent rendering across browsers. Some browsers may silently discard the invalid declaration, while others might apply unexpected fallback behavior. Keeping your CSS valid ensures predictable, cross-browser results and makes your stylesheets easier to maintain and debug.
How to Fix It
- Add the missing numeric value — Pair every unit with a number, e.g.,
300px,1.5em,50%. - Use
0without a unit for zero values — The value0is valid on its own and doesn't require a unit. - Use a valid keyword — Properties like
widthaccept keywords such asauto,min-content,max-content, andfit-content. - Check dynamic values — If a preprocessor or template engine generates the value, verify it outputs a complete length (e.g.,
${value}pxwherevalueis not empty).
Examples
Incorrect: unit without a number
<style>
.box {
width: px;
}
</style>
<div class="box">Content</div>
The declaration width: px is invalid because px alone is not a recognized CSS value. The browser will discard this rule.
Incorrect: number without a unit
<style>
.box {
width: 300;
}
</style>
<div class="box">Content</div>
A unitless number (other than 0) is also invalid for the width property. Browsers will ignore this declaration as well.
Correct: number paired with a unit
<style>
.box {
width: 300px;
}
</style>
<div class="box">Content</div>
Correct: using different valid length units
<style>
.box-a {
width: 50%;
}
.box-b {
width: 20em;
}
.box-c {
width: 15rem;
}
.box-d {
width: 80vw;
}
</style>
Correct: zero value and keywords
<style>
.collapsed {
width: 0;
}
.flexible {
width: auto;
}
.intrinsic {
width: fit-content;
}
</style>
The value 0 is the only number that doesn't require a unit in CSS. Keywords like auto, min-content, max-content, and fit-content are also valid for width and don't use numeric lengths at all.
Common CSS Length Units
| Unit | Description |
|---|---|
px | Pixels (absolute unit) |
em | Relative to the element's font size |
rem | Relative to the root element's font size |
% | Percentage of the containing block's dimension |
vw | 1% of the viewport width |
vh | 1% of the viewport height |
ch | Width of the "0" character in the element's font |
Always double-check that your CSS length values include both a number and a unit. If you're generating styles dynamically, add safeguards to ensure the numeric portion is never empty before the unit is appended.
An invalid value was assigned to the CSS right property, meaning the validator does not recognize the value you provided.
The CSS right property specifies the horizontal offset of a positioned element from the right edge of its containing block. It only accepts specific value types: a length (e.g., 10px, 2em), a percentage (e.g., 50%), auto, inherit, initial, unset, or revert. Any other value — such as a typo, a missing unit, or an unsupported keyword — will trigger this validation error.
A common mistake is forgetting the unit after a number. In CSS, 0 is the only length value that can be written without a unit. Writing something like right: 10 instead of right: 10px is invalid. Another common cause is using an unrecognized keyword or passing a value meant for a different property.
Invalid Example
<div style="position: absolute; right: 10;">
This box has an invalid right value.
</div>
The value 10 is missing a unit, so the validator rejects it.
Fixed Example
<div style="position: absolute; right: 10px;">
This box is correctly positioned.
</div>
Adding a valid unit like px, em, rem, or % resolves the issue. If you intended no offset, use right: 0 or right: auto.
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
<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.
The text-align CSS property controls horizontal alignment of inline-level content within a block element. When the W3C HTML validator encounters an inline style attribute containing a text-align value it doesn't recognize, it flags the error with a message like CSS: "text-align": X is not a "text-align" value, where X is the offending value.
This error commonly occurs for a few reasons:
- Confusing
text-alignwithvertical-align: Usingmiddle,top, orbottom— these arevertical-alignvalues, nottext-alignvalues. - Typos: Writing
cetnerinstead ofcenter, orrigthinstead ofright. - Using non-standard values: Trying values like
auto,none, or arbitrary strings that aren't part of the specification. - Confusing CSS properties: Using Flexbox or Grid alignment values like
flex-startorspace-betweenwithtext-align.
While most browsers will silently ignore an invalid text-align value and fall back to the inherited or default alignment, relying on this behavior is problematic. It makes your intent unclear, can lead to inconsistent rendering, and signals that there may be a deeper misunderstanding in your styling approach. Valid CSS ensures predictable behavior across all browsers and assistive technologies.
Valid values for text-align
| Value | Description |
|---|---|
left | Aligns content to the left edge |
right | Aligns content to the right edge |
center | Centers the content horizontally |
justify | Stretches content to fill the full width |
start | Aligns to the start edge (direction-aware) |
end | Aligns to the end edge (direction-aware) |
The start and end values are logical properties that respect the document's writing direction (dir attribute or direction CSS property), making them ideal for internationalized content.
Examples
Invalid: using middle instead of center
A common mistake is using middle, which is a valid value for vertical-align but not for text-align:
<p style="text-align: middle;">This text will fail validation.</p>
Fix: Replace middle with center:
<p style="text-align: center;">This text is properly centered.</p>
Invalid: typo in the value
<h2 style="text-align: cetner;">Heading</h2>
Fix: Correct the spelling:
<h2 style="text-align: center;">Heading</h2>
Invalid: using a non-existent value
<div style="text-align: auto;">Some content</div>
Fix: Choose a valid alignment value:
<div style="text-align: left;">Some content</div>
Invalid: using a vertical alignment value
<p style="text-align: top;">Paragraph text</p>
Fix: If you intended horizontal alignment, use a valid text-align value. If you actually need vertical positioning, use vertical-align on an inline or table-cell element instead:
<p style="text-align: left;">Paragraph text</p>
Valid examples showing all common values
<p style="text-align: left;">Left-aligned text.</p>
<p style="text-align: right;">Right-aligned text.</p>
<p style="text-align: center;">Centered text.</p>
<p style="text-align: justify;">Justified text stretches to fill the full width of its container.</p>
<p style="text-align: start;">Start-aligned (respects text direction).</p>
<p style="text-align: end;">End-aligned (respects text direction).</p>
When fixing this error, double-check which property you actually need. If you want to center a block-level element itself (not its text content), text-align isn't the right tool — consider using margin: 0 auto or Flexbox instead. The text-align property is specifically for the horizontal alignment of inline content within its containing block.
The value inter-ideograph is not part of the CSS text-justify specification and will not pass validation.
The text-justify property controls how the browser distributes spacing when text-align is set to justify. The value inter-ideograph was an Internet Explorer proprietary extension that added extra spacing between ideographic (CJK) characters and words. It was never standardized by the W3C and is not recognized by modern browsers.
The valid values for text-justify in the current CSS Text Module Level 3 specification are:
auto— the browser chooses the best justification method based on the content language. For CJK text, most browsers already apply inter-ideograph-style spacing automatically with this value.inter-word— spacing is distributed between words only (best for Latin scripts).inter-character— spacing is distributed between characters (useful for CJK text).none— disables justification.
For content that mixes CJK and Latin text, auto is usually the best choice, since the browser will apply appropriate spacing rules for each script.
HTML examples
Before (invalid)
<p style="text-align: justify; text-justify: inter-ideograph;">
This text uses a non-standard value.
</p>
After (valid)
<p style="text-align: justify; text-justify: auto;">
This text uses a valid value. The browser applies
appropriate justification based on the content language.
</p>
The text-overflow CSS property controls how overflowed content that is not displayed is signaled to users. It applies when an element's overflow is hidden (e.g., overflow: hidden) and the content exceeds the element's box. The property accepts specific values, and using anything outside the allowed set — such as a misspelled keyword, an unquoted string, or a made-up value — will trigger this validation error.
Accepted values
The text-overflow property accepts the following values:
clip— Truncates the text at the edge of the content area. Characters may be clipped mid-glyph. This is the default.ellipsis— Displays an ellipsis character (…) to indicate clipped text.- A custom
<string>— A quoted string to display at the clipping point (e.g.," [..]"). Note that browser support for custom strings is limited. - Global CSS values —
inherit,initial,revert,revert-layer, andunset.
The property can take one or two values. If one value is given, it specifies the overflow behavior for the end of the line (the right end for left-to-right text, the left end for right-to-left text). If two values are given, the first controls the left end of the line and the second controls the right end. Two-value syntax has limited browser support.
Common mistakes that trigger this error include:
- Misspelling
ellipsis(e.g.,elipsis,ellipses). - Using a value from a different property (e.g.,
hidden,scroll,auto). - Using an unquoted custom string instead of a properly quoted one.
- Using a numeric or length value (e.g.,
10px), which is not valid for this property.
Why this matters
Invalid CSS values are ignored by browsers, which means the property will fall back to its default (clip) instead of behaving as you intended. This can lead to text being abruptly cut off without any visual indicator, harming readability and user experience. Fixing validation errors also ensures your stylesheets are clean, predictable, and maintainable.
Examples
Incorrect — misspelled keyword
/* "elipsis" is not a valid text-overflow value */
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: elipsis;
}
Incorrect — value from another property
/* "hidden" is an overflow value, not a text-overflow value */
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: hidden;
}
Incorrect — unquoted custom string
/* Custom strings must be quoted */
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: [more];
}
Correct — using ellipsis
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
Correct — using clip (the default)
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}
Correct — using a quoted custom string
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: " [..]";
}
Correct — two-value syntax
/* Left end uses ellipsis, right end uses a custom string */
.truncated {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis " [..]";
}
Note that text-overflow on its own does not force overflow to occur. To make text actually truncate, you typically need overflow: hidden (or another non-visible overflow value) and white-space: nowrap on the element. The text-overflow property only controls how the clipped content is signaled visually.
The text-transform CSS property controls the capitalization of text within an element. It's commonly used to enforce consistent text casing — for example, making headings appear in all uppercase or ensuring navigation links are lowercase — without changing the actual content in the HTML. When the validator encounters a value it doesn't recognize for this property, it flags it as invalid.
This error can occur for several reasons:
- Typos — writing
upppercaseinstead ofuppercase, orCapitalizeinstead ofcapitalize(CSS values are case-sensitive in validation contexts). - Incorrect values — using values from other properties, like
bold,italic, orcenter, which don't apply totext-transform. - Non-standard values — using browser-specific or experimental values that aren't part of the CSS specification.
- Wrong property — confusing
text-transformwithtext-decoration,text-align, orfont-variant, and using their values here instead.
Fixing this matters because invalid CSS can lead to unpredictable rendering across browsers. While most browsers will simply ignore an invalid declaration, your intended styling won't be applied, potentially breaking your design. Keeping your CSS valid also improves maintainability and ensures forward compatibility.
Valid values for text-transform
| Value | Effect |
|---|---|
none | No capitalization change (default) |
capitalize | First letter of each word is uppercased |
uppercase | All characters are converted to uppercase |
lowercase | All characters are converted to lowercase |
full-width | Forces characters into a full-width form (useful for CJK typography) |
full-size-kana | Converts small kana characters to full-size equivalents |
Examples
Incorrect — invalid value
In this example, bold is not a valid text-transform value. It likely belongs on the font-weight property instead.
<p style="text-transform: bold;">Welcome to our site</p>
Similarly, a simple typo will trigger this error:
<p style="text-transform: uppercse;">Welcome to our site</p>
Correct — using valid values
<p style="text-transform: uppercase;">Welcome to our site</p>
<p style="text-transform: capitalize;">Welcome to our site</p>
Correct — separating concerns with the right properties
If you intended to make text bold and uppercase, use the appropriate property for each effect:
<p style="font-weight: bold; text-transform: uppercase;">Welcome to our site</p>
Correct — using text-transform in a stylesheet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Text Transform Example</title>
<style>
.heading {
text-transform: uppercase;
}
.name {
text-transform: capitalize;
}
.code-snippet {
text-transform: none;
}
</style>
</head>
<body>
<h1 class="heading">site navigation</h1>
<p class="name">john doe</p>
<code class="code-snippet">myVariable</code>
</body>
</html>
If you're unsure which value you need, uppercase and capitalize are the most commonly used. Use none when you need to override a text-transform rule inherited from a parent element.
The @charset rule in a <style> element is not valid because @charset only applies to external CSS files, not inline styles.
When CSS is embedded inside a <style> element, the character encoding is already determined by the HTML document itself (through the <meta charset> declaration or the HTTP Content-Type header). The @charset rule has no effect in this context, and the W3C validator flags it as an error.
This error also appears when @charset is present in an external .css file but is not on the very first line, or has a space or BOM (byte order mark) before it. In external stylesheets, @charset must be the absolute first thing in the file, with no preceding whitespace or comments.
How to fix it
If the @charset rule is inside a <style> element, remove it:
<!-- Wrong: @charset inside a style element -->
<style>
@charset "UTF-8";
body {
color: #333;
}
</style>
<!-- Fixed: remove the @charset rule -->
<style>
body {
color: #333;
}
</style>
If the @charset rule is in an external stylesheet, make sure it is on the very first line with no preceding spaces, BOM characters, or comments:
@charset "UTF-8";
body {
color: #333;
}
The word-break property controls how words break when they would overflow their container. While break-word was historically used as a value for this property, the CSS Text Module Level 3 specification has deprecated it. As the spec explains:
For compatibility with legacy content, the word-break property also supports a deprecated
break-wordkeyword. When specified, this has the same effect asword-break: normalandoverflow-wrap: anywhere, regardless of the actual value of theoverflow-wrapproperty.
Browsers still support word-break: break-word for backward compatibility, but relying on deprecated values is risky. Future browser versions may remove support, and validators will flag it as an issue. The correct approach is to use the overflow-wrap property instead, which is specifically designed to control whether and how words break when they overflow their container.
There are two modern alternatives to consider:
overflow-wrap: break-word— allows the browser to break an otherwise unbreakable word at an arbitrary point to prevent overflow. The word only breaks if it cannot fit on its own line. This is the most widely supported option.overflow-wrap: anywhere— works likebreak-wordbut also allows the broken word fragments to be considered during min-content sizing calculations. This means containers usingmin-contentorfit-contentwidths can shrink further.
In most cases, overflow-wrap: break-word is the right replacement.
Examples
Deprecated usage
This triggers the W3C CSS validation warning:
<div style="word-break: break-word;">
Thisislongtext_thatdoesnotcontainanyspaces_andshouldbreakproperly
</div>
Fixed with overflow-wrap: break-word
This is the most common and well-supported fix:
<div style="word-break: normal; overflow-wrap: break-word;">
Thisislongtext_thatdoesnotcontainanyspaces_andshouldbreakproperly
</div>
Fixed with overflow-wrap: anywhere
Use this if you also want min-content sizing to account for the broken fragments:
<div style="word-break: normal; overflow-wrap: anywhere;">
Thisislongtext_thatdoesnotcontainanyspaces_andshouldbreakproperly
</div>
In a stylesheet
If the deprecated value appears in a CSS file or <style> block, apply the same fix:
<style>
/* Deprecated */
/*
.content {
word-break: break-word;
}
*/
/* Correct */
.content {
word-break: normal;
overflow-wrap: break-word;
}
</style>
<div class="content">
Thisislongtext_thatdoesnotcontainanyspaces_andshouldbreakproperly
</div>
Understanding the difference between overflow-wrap values
<style>
.container {
width: min-content;
border: 1px solid black;
margin-bottom: 1rem;
}
.wrap-break-word {
overflow-wrap: break-word;
}
.wrap-anywhere {
overflow-wrap: anywhere;
}
</style>
<!-- With break-word, min-content width is based on the longest word -->
<div class="container wrap-break-word">
Short words here but_a_very_long_unbreakable_word_too
</div>
<!-- With anywhere, min-content can shrink past even long words -->
<div class="container wrap-anywhere">
Short words here but_a_very_long_unbreakable_word_too
</div>
With overflow-wrap: break-word, the container's min-content width will be determined by the longest unbreakable string. With overflow-wrap: anywhere, even that long string can be broken, allowing the container to shrink further. For most use cases where you simply want to prevent text from overflowing a fixed-width container, overflow-wrap: break-word is the straightforward choice.
A percentage value in your CSS exceeds the allowed range of 0 to 100.
CSS properties that accept percentage values — such as opacity, width, height, and others used in certain contexts — may be restricted to specific ranges. When you embed CSS in an HTML document (via the style attribute or a <style> element), the W3C HTML Validator checks these values and flags any that fall outside the permitted range.
A value like 100.01% is just slightly over the maximum of 100%. This is often a typo or a rounding error. While most browsers will silently clamp the value to 100%, it is still invalid and should be corrected.
HTML Examples
❌ Invalid: value out of range
<div style="width: 100.01%;">Content</div>
✅ Fixed: value within range
<div style="width: 100%;">Content</div>
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.,
rotateZtyped asrotatezin some contexts, orskewtyped asskeew). - Too many arguments are passed to a transform function (e.g.,
rotate(45deg, 20deg)instead ofrotate(45deg)). - Arguments are missing required units (e.g.,
rotate(45)instead ofrotate(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-transformsyntax are used in the standardtransformproperty 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)ortranslate(tx, ty)— lengths or percentagestranslateX(tx),translateY(ty),translateZ(tz)— a single length/percentagescale(sx)orscale(sx, sy)— unitless numbersscaleX(sx),scaleY(sy),scaleZ(sz)— a single unitless numberrotate(angle)— a single angle value (e.g.,45deg)rotateX(angle),rotateY(angle),rotateZ(angle)— a single angleskew(ax)orskew(ax, ay)— angle valuesskewX(ax),skewY(ay)— a single anglematrix(a, b, c, d, tx, ty)— exactly six unitless numbersmatrix3d(...)— 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 transform CSS property lets you rotate, scale, skew, or translate an element by modifying its coordinate space. The validator checks inline and embedded CSS for correctness, and it will flag any value it doesn't recognize as a valid transform value. Common mistakes include:
- Missing units on angles or lengths (e.g.,
rotate(45)instead ofrotate(45deg)) - Typos in function names (e.g.,
rotatee(10deg)ortranlate(10px)) - Wrong value types (e.g., using a color or a plain number where a function is expected)
- Missing commas or parentheses in function arguments
- Using non-existent functions (e.g.,
flip(180deg)is not a valid transform function) - Incorrect number of arguments (e.g.,
matrix()requires exactly 6 values)
This matters for standards compliance and predictable rendering. While browsers may silently ignore invalid transform values, the element simply won't be transformed — which can lead to subtle layout bugs that are hard to track down. Catching these errors at validation time helps you fix them before they reach users.
Examples
Invalid: missing angle unit
The rotate() function requires a value with an angle unit like deg, rad, turn, or grad.
<div style="transform: rotate(45);">Rotated text</div>
Fixed: adding the angle unit
<div style="transform: rotate(45deg);">Rotated text</div>
Invalid: typo in function name
<div style="transform: tranlateX(10px);">Shifted text</div>
Fixed: correcting the function name
<div style="transform: translateX(10px);">Shifted text</div>
Invalid: using a non-transform value
A plain number or unrelated keyword is not a valid transform value.
<div style="transform: 200px;">Content</div>
Fixed: using a proper transform function
<div style="transform: translateX(200px);">Content</div>
Invalid: wrong number of arguments for matrix()
The matrix() function requires exactly six comma-separated numbers.
<div style="transform: matrix(1, 2, 3);">Content</div>
Fixed: providing all six arguments
<div style="transform: matrix(1, 0, 0, 1, 0, 0);">Content</div>
Valid transform values reference
Here is a summary of all valid transform functions and the keyword/global values:
<style>
/* Keyword value */
.no-transform { transform: none; }
/* Translate functions */
.move-a { transform: translate(12px, 50%); }
.move-b { transform: translateX(2em); }
.move-c { transform: translateY(3in); }
.move-d { transform: translateZ(2px); }
.move-e { transform: translate3d(12px, 50%, 3em); }
/* Rotate functions */
.spin-a { transform: rotate(0.5turn); }
.spin-b { transform: rotateX(10deg); }
.spin-c { transform: rotateY(10deg); }
.spin-d { transform: rotateZ(10deg); }
.spin-e { transform: rotate3d(1, 2, 3, 10deg); }
/* Scale functions */
.grow-a { transform: scale(2, 0.5); }
.grow-b { transform: scaleX(2); }
.grow-c { transform: scaleY(0.5); }
.grow-d { transform: scaleZ(0.3); }
.grow-e { transform: scale3d(2.5, 1.2, 0.3); }
/* Skew functions */
.lean-a { transform: skew(30deg, 20deg); }
.lean-b { transform: skewX(30deg); }
.lean-c { transform: skewY(1.07rad); }
/* Other functions */
.depth { transform: perspective(500px); }
.matrix-2d { transform: matrix(1, 0, 0, 1, 0, 0); }
.matrix-3d { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
/* Multiple functions chained together */
.combo { transform: translateX(10px) rotate(10deg) translateY(5px); }
</style>
When troubleshooting this error, look at the specific value the validator reports as invalid. Compare it against the valid functions listed above, double-check spelling, ensure all arguments have correct units, and verify that parentheses and commas are properly placed.
In CSS, most numeric values of 0 don't need a unit — for example, margin: 0 is perfectly valid because the specification allows unitless zero for <length> values. However, this exception does not apply to <time> values. Properties that accept <time> values, such as transition-delay, transition-duration, animation-delay, and animation-duration, always require a unit (s for seconds or ms for milliseconds), even when the value is zero.
The CSS specification explicitly states that <time> values must include a unit. The unitless 0 shorthand is only permitted for <length> and a few other value types. While some browsers may silently accept transition-delay: 0 and treat it as 0s, this behavior is non-standard and not guaranteed across all browsers or future implementations. Relying on it can lead to inconsistent rendering and will fail W3C CSS validation.
This issue commonly appears when transition-delay is set as part of the transition shorthand, or when developers assume that 0 is universally valid without a unit in CSS.
How to fix it
Add the s (seconds) or ms (milliseconds) unit to any <time> value that is currently a bare 0:
0→0sor0ms- Check both longhand properties (
transition-delay,transition-duration) and thetransitionshorthand.
Examples
Incorrect — unitless zero
<style>
.fade {
transition-delay: 0;
transition-duration: 0.3s;
transition-property: opacity;
}
</style>
<div class="fade">Hello</div>
The validator reports: CSS: "transition-delay": "0" is not a "transition-delay" value.
Correct — with time unit
<style>
.fade {
transition-delay: 0s;
transition-duration: 0.3s;
transition-property: opacity;
}
</style>
<div class="fade">Hello</div>
Incorrect — unitless zero in the transition shorthand
<style>
.btn {
transition: background-color 0.2s ease 0;
}
</style>
<button class="btn">Click me</button>
The fourth value in the transition shorthand is the delay, and 0 without a unit is invalid.
Correct — shorthand with time unit
<style>
.btn {
transition: background-color 0.2s ease 0s;
}
</style>
<button class="btn">Click me</button>
Multiple transitions
When specifying delays for multiple properties, ensure every <time> value has a unit:
<style>
.card {
transition-property: opacity, transform;
transition-duration: 0.3s, 0.5s;
transition-delay: 0s, 0.1s;
}
</style>
<div class="card">Content</div>
The same rule applies to transition-duration and the animation-delay and animation-duration properties — always include s or ms, even for zero values.
The ::file-selector-button pseudo-element targets the button inside an <input type="file"> element — the part users click to open the file picker dialog. It is supported in all major modern browsers and is part of the CSS Pseudo-Elements Level 4 specification.
The W3C CSS Validator has not yet been updated to recognize ::file-selector-button, which causes it to flag the pseudo-element as invalid. This is a known bug that has been reported on GitHub. Because the validator's CSS grammar definitions lag behind the living standards, certain newer — but fully standardized — features trigger false positives.
Before ::file-selector-button was standardized, browsers used vendor-prefixed versions like ::-webkit-file-upload-button and ::-ms-browse. The unprefixed ::file-selector-button replaced these and is now the recommended approach.
What you should do
Since this is a validator limitation rather than an actual code issue, you can safely ignore this warning. Your CSS is standards-compliant. If you want to suppress the warning in a CI/CD pipeline or validation report, you can add a comment noting the false positive, but do not remove or change the pseudo-element — it is correct.
Browser support
::file-selector-button is supported in Chrome 89+, Firefox 82+, Safari 14.1+, and Edge 89+. For older browsers, you may include the vendor-prefixed versions as fallbacks, though these will also trigger validator warnings.
Examples
CSS that triggers the validator warning
::file-selector-button {
background-color: #4a90d9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
This CSS is valid and correct despite the validator warning. It styles the file selector button with a custom background color, text color, and border radius.
Scoping to a specific input
input[type="file"]::file-selector-button {
background-color: #4a90d9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
input[type="file"]::file-selector-button:hover {
background-color: #357abd;
}
Including vendor-prefixed fallbacks for older browsers
input[type="file"]::-webkit-file-upload-button {
background-color: #4a90d9;
color: white;
border: none;
padding: 8px 16px;
}
input[type="file"]::file-selector-button {
background-color: #4a90d9;
color: white;
border: none;
padding: 8px 16px;
}
The vendor-prefixed version is placed first so that the standard ::file-selector-button rule takes precedence in browsers that support both. Note that the vendor-prefixed versions will also trigger validator warnings for the same reason.
Corresponding HTML
<label for="upload">Choose a file:</label>
<input type="file" id="upload" name="upload">
There is nothing to fix in your HTML or CSS. This is purely a validator false positive that will be resolved when the W3C Validator updates its CSS grammar definitions.
CSS distinguishes between pseudo-classes and pseudo-elements using different colon syntax. Pseudo-classes like :hover, :focus, and :active describe a temporary state of an element and use a single colon (:). Pseudo-elements like ::before, ::after, and ::first-line target a specific part of an element's rendering and use double colons (::). Writing ::hover conflates these two concepts — there is no pseudo-element called hover in any CSS specification.
This matters for several reasons. First, most browsers will silently ignore the invalid ::hover rule entirely, meaning your hover styles simply won't apply. Users will see no visual feedback when hovering over interactive elements like links and buttons, which hurts usability. Second, the lack of hover feedback can be an accessibility concern — sighted users rely on hover states to identify clickable elements. Third, invalid CSS can cause unpredictable behavior across different browsers and versions, making your site harder to maintain.
The confusion often arises because CSS2 originally allowed single colons for pseudo-elements (e.g., :before), and CSS3 introduced the double-colon syntax to clearly separate pseudo-elements from pseudo-classes. This means you might see both :before and ::before in the wild, which can make it tempting to assume that double colons work everywhere. The key rule to remember: states use one colon (:hover, :focus, :visited), and sub-element targets use two colons (::before, ::after, ::placeholder).
Examples
Incorrect: using double colons with hover
a::hover {
color: red;
}
button::hover {
background-color: blue;
}
Both rules above will trigger the validation error and will likely be ignored by browsers.
Correct: using a single colon with hover
a:hover {
color: red;
}
button:hover {
background-color: blue;
}
Correct usage of pseudo-classes vs. pseudo-elements
This example demonstrates how single-colon pseudo-classes and double-colon pseudo-elements are used together correctly:
a:hover {
color: red;
}
a:focus {
outline: 2px solid blue;
}
a::before {
content: "→ ";
}
Full HTML document with valid :hover usage
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hover Example</title>
<style>
a:hover {
color: red;
text-decoration: underline;
}
</style>
</head>
<body>
<a href="#">Hover over this link</a>
</body>
</html>
Quick Reference
| Type | Syntax | Examples |
|---|---|---|
| Pseudo-class (state) | Single colon : | :hover, :focus, :active, :visited, :first-child |
| Pseudo-element (sub-part) | Double colon :: | ::before, ::after, ::first-line, ::placeholder |
If you encounter this validation error, search your stylesheets for ::hover and replace every instance with :hover. The same fix applies if you accidentally use double colons with other pseudo-classes like ::focus or ::active.
::ng-deep is a pseudo-class combinator specific to Angular's view encapsulation system. It tells Angular's compiler to disable style encapsulation for a particular CSS rule, allowing that rule to penetrate into child component views. Because it is not defined in any W3C or WHATWG CSS specification, the CSS validator flags it as an unknown pseudo-element or pseudo-class.
This is important to understand for several reasons:
- It's not a standard CSS feature. No browser natively understands
::ng-deep. Angular's build toolchain processes and removes it at compile time, rewriting the selector before it reaches the browser. The validator checks against CSS specifications and rightfully does not recognize it. - It's deprecated within Angular itself. The Angular team has deprecated
::ng-deepand plans to remove it in a future version. Continued use ties your codebase to a feature with no long-term support. - It can cause unintended global style leakage. When combined with
:hostimproperly or used without care,::ng-deepcan bleed styles into components where they weren't intended, making your application harder to maintain.
How to Fix It
There are several approaches depending on your situation:
1. Accept the Validation Warning
If you're working in an Angular project and need ::ng-deep temporarily, you can acknowledge this as a known framework-specific warning. The validator is technically correct — the selector isn't valid CSS — but Angular's compiler handles it before it reaches the browser.
2. Use Global Stylesheets
Move the styles that need to cross component boundaries into a global stylesheet (like styles.css). Global styles are not encapsulated and naturally apply to all components.
3. Set ViewEncapsulation to None
Disable view encapsulation on the component that needs to style its children. This makes all of that component's styles global in scope, removing the need for ::ng-deep.
4. Use CSS Custom Properties (Recommended)
The most standards-compliant approach is to use CSS custom properties (variables) to create a theming API for your components. Custom properties naturally inherit through the DOM tree, crossing shadow DOM and Angular encapsulation boundaries.
Examples
❌ Using ::ng-deep (Triggers the Validation Warning)
:host ::ng-deep .child-button {
background-color: blue;
color: white;
}
The validator does not recognize ::ng-deep and reports the error.
✅ Using a Global Stylesheet Instead
In your global styles.css file, target the element with a specific class or selector:
app-parent .child-button {
background-color: blue;
color: white;
}
This avoids ::ng-deep entirely by placing the rule outside of component-scoped styles.
✅ Using CSS Custom Properties
Define customizable properties in the child component's CSS:
/* Child component styles */
.child-button {
background-color: var(--child-button-bg, gray);
color: var(--child-button-color, black);
}
Then set the values from the parent component's CSS:
/* Parent component styles */
:host {
--child-button-bg: blue;
--child-button-color: white;
}
This approach is fully standards-compliant, passes CSS validation, and creates a clean styling API between components. CSS custom properties inherit naturally through the DOM, so they work across component boundaries without breaking encapsulation.
✅ Using ::part() for Web Components
If your child components use shadow DOM (or you're migrating toward web components), the standard ::part() pseudo-element lets you expose specific elements for external styling:
/* Parent styles targeting an exposed part */
child-component::part(button) {
background-color: blue;
color: white;
}
The ::part() pseudo-element is a W3C standard and fully recognized by the CSS validator.
CSS pseudo-classes (like :hover, :focus, :nth-child()) select elements based on their state or position, while pseudo-elements (like ::before, ::after, ::placeholder) target specific parts of an element. The CSS specification distinguishes between the two by using a single colon for pseudo-classes and a double colon for pseudo-elements. While browsers still support the legacy single-colon syntax for older pseudo-elements like :before and :after for backward compatibility, the validator expects the modern double-colon form ::before and ::after.
Why This Matters
Standards compliance. The double-colon syntax for pseudo-elements was introduced in CSS3 to clearly distinguish pseudo-elements from pseudo-classes. Using the correct syntax makes your code more readable and future-proof.
Catching real bugs. A misspelled pseudo-class like :foucs or :hovr will silently fail — the browser simply ignores the entire rule. The validator catches these typos before they cause mysterious styling issues in production.
Validator profile limitations. The W3C CSS validator checks your styles against a specific CSS profile. Newer selectors like :has(), :is(), or :where() may not be recognized if the validator is set to an older profile like CSS Level 2.1 or even CSS Level 3. Vendor-prefixed selectors like ::-webkit-input-placeholder or ::-moz-placeholder are never part of any standard profile and will always be flagged.
Common Causes
- Typos —
:hovr,:foucs,::plceholder, etc. - Single colon on pseudo-elements —
:before,:after,:first-line,:first-letterinstead of their double-colon equivalents. - Vendor-prefixed selectors —
::-webkit-input-placeholder,::-moz-selection,:-ms-input-placeholder. - Modern selectors on older profiles —
:has(),:is(),:where(),::marker,:focus-visiblemay not be recognized depending on the validator's CSS level setting.
How to Fix
- Check spelling of all pseudo-classes and pseudo-elements.
- Use double colons for pseudo-elements:
::before,::after,::first-line,::first-letter,::placeholder,::marker,::selection. - Replace vendor-prefixed selectors with their standard equivalents. If you still need the prefix for browser support, place the standard version alongside it and accept that the prefixed line may produce a warning.
- Update the validator profile to a newer CSS level if you're intentionally using modern selectors like
:has()or:focus-visible.
Examples
Incorrect — triggers the warning
<style>
/* Typo in pseudo-class */
a:hovr {
color: red;
}
/* Single colon on pseudo-element */
p:before {
content: "→ ";
}
/* Vendor-prefixed pseudo-element without standard version */
input::-webkit-input-placeholder {
color: gray;
}
</style>
Each of these rules will trigger an "Unknown pseudo-element or pseudo-class" warning. The first is a simple typo, the second uses outdated single-colon syntax, and the third is a non-standard vendor prefix.
Correct — valid CSS
<style>
/* Fixed typo */
a:hover {
color: red;
}
/* Double colon for pseudo-element */
p::before {
content: "→ ";
}
/* Standard pseudo-element */
input::placeholder {
color: gray;
}
</style>
Handling modern selectors
Some modern pseudo-classes like :has() and :focus-visible are well-supported in browsers but may not yet be recognized by the validator. If you need to use them, you can acknowledge the warning or structure your CSS so the modern selector enhances rather than replaces base styles:
<style>
/* Base style that always applies */
.card {
border: 1px solid transparent;
}
/* Enhancement using :has() — may warn in the validator */
.card:has(img) {
border-color: #ccc;
}
/* :focus-visible for keyboard-only focus rings */
button:focus-visible {
outline: 2px solid blue;
}
</style>
These selectors are valid CSS and work in modern browsers. If the validator flags them, consider switching the validator's profile to the latest CSS level, or treat the warnings as informational rather than errors.
The @container at-rule enables container queries, allowing you to apply styles to elements based on the size or inline-size of a parent container rather than the viewport. It was introduced as part of the CSS Containment Module Level 3 specification and has been supported in all major browsers (Chrome, Edge, Firefox, Safari) since early 2023.
The W3C CSS validator uses its own CSS parsing engine, which sometimes lags behind the latest CSS specifications. Because @container is relatively new compared to long-established at-rules like @media or @keyframes, the validator may flag it as unrecognized. This does not mean your CSS is invalid or broken — it simply means the validator hasn't caught up with the spec yet.
Why This Warning Appears
The W3C validator checks your CSS against known grammar rules. When it encounters an at-rule it doesn't have in its internal dictionary, it reports it as unrecognized. Other modern CSS features like @layer and @property have historically triggered the same kind of false positive before being added to the validator's parser.
Since this is a validator limitation and not an actual code issue, there's no required "fix." However, you should still make sure your @container usage is syntactically correct and that you've properly set up containment on the parent element.
How Container Queries Work
For @container to function, a parent element must be designated as a containment context using the container-type property (or the container shorthand). Without this, the browser won't know which ancestor to query against.
Examples
Correct usage of @container
The parent element needs container-type set to inline-size or size so its descendants can query against it:
<div class="card-wrapper">
<div class="card">
<h2>Title</h2>
<p>Some content here.</p>
</div>
</div>
.card-wrapper {
container-type: inline-size;
container-name: card-container;
}
@container card-container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
}
In this example, .card-wrapper is established as a containment context. When its inline size is at least 400px, the .card inside switches to a flex layout. The validator may flag the @container block, but this CSS is perfectly valid and works in all modern browsers.
Using @container without a named container
You can omit the container name, and the query will match the nearest ancestor with a container-type set:
.sidebar {
container-type: inline-size;
}
@container (max-width: 300px) {
.sidebar-nav {
flex-direction: column;
}
}
Common mistake: forgetting container-type
If you use @container without declaring a containment context on a parent, the query will never match and your styles won't apply. This won't cause a validator error, but it's a logical bug:
/* Missing container-type — the @container query has nothing to query against */
.wrapper {
max-width: 600px;
}
@container (min-width: 400px) {
.content {
font-size: 1.25rem;
}
}
The fix is to add container-type to the intended parent:
.wrapper {
max-width: 600px;
container-type: inline-size;
}
@container (min-width: 400px) {
.content {
font-size: 1.25rem;
}
}
What You Should Do
- Verify your syntax — make sure you're using
container-typeon a parent element and that your@containerquery follows the correct grammar. - Ignore the validator warning — this is a known limitation of the W3C CSS validator. Your CSS is valid per the specification.
- Check browser support —
@containeris supported in Chrome 105+, Edge 105+, Firefox 110+, and Safari 16+. If you need to support older browsers, consider using@containeras a progressive enhancement alongside a fallback layout.
The @font-feature-values at-rule lets you define human-readable names for OpenType font feature indexes, which you can then reference using properties like font-variant-alternates. For example, instead of remembering that swash index 1 maps to a "fancy" style, you can define a named value and use it semantically throughout your CSS. This is a legitimate and useful feature defined in the CSS Fonts Module Level 4 specification.
The validation error occurs because the W3C CSS validator does not always keep pace with newer CSS specifications. The @font-feature-values rule, along with its associated feature type blocks like @swash, @styleset, @character-variant, @ornaments, @annotation, and @stylistic, may simply not be in the validator's recognized grammar yet. This does not mean your CSS is broken or invalid — it means the validator has a gap in its coverage.
That said, there are practical reasons to consider alternatives. If you need to pass strict W3C validation (for example, as a project requirement or contractual obligation), or if you need to support older browsers that lack @font-feature-values support, the font-feature-settings property offers a more widely recognized way to activate OpenType features. The tradeoff is that font-feature-settings uses raw four-character OpenType feature tags instead of friendly names, making it less readable but more portable.
How to Fix
You have several options:
- Ignore the warning. If your target browsers support
@font-feature-values, the CSS is valid per the spec. The validator error is a false positive. - Move the at-rule to an external stylesheet. If you're validating HTML and the CSS is in a
<style>block, moving it to an external.cssfile may help you separate concerns and skip CSS validation during HTML checks. - Replace with
font-feature-settings. Use the lower-level property to activate OpenType features directly by their tag codes.
Examples
Code that triggers the validation error
@font-feature-values "MyFamily" {
@swash {
fancy: 1;
}
}
p {
font-family: "MyFamily", serif;
font-variant-alternates: swash(fancy);
}
The validator does not recognize @font-feature-values and flags it as an error, even though this is spec-compliant CSS.
Fixed: Using font-feature-settings instead
<!DOCTYPE html>
<html lang="en">
<head>
<title>Font Feature Example</title>
<style>
p {
font-family: "MyFamily", serif;
font-feature-settings: "swsh" 1;
}
</style>
</head>
<body>
<p>This text uses OpenType swash glyphs via font-feature-settings.</p>
</body>
</html>
The font-feature-settings property accepts OpenType feature tags directly. Common tags include "swsh" for swashes, "smcp" for small caps, "liga" for standard ligatures, and "onum" for oldstyle numerals. This approach avoids the unrecognized at-rule error entirely.
Fixed: Keeping @font-feature-values with a fallback
If you want to use the more readable @font-feature-values syntax while also providing fallback support, you can combine both approaches:
p {
font-family: "MyFamily", serif;
font-feature-settings: "swsh" 1;
font-variant-alternates: swash(fancy);
}
@font-feature-values "MyFamily" {
@swash {
fancy: 1;
}
}
Browsers that understand font-variant-alternates and @font-feature-values will use the named value. Others will fall back to font-feature-settings. The validation error will persist with this approach, but your CSS will be robust and spec-compliant regardless.
The @if at-rule is not part of the CSS specification and is not recognized by browsers or the W3C validator.
CSS does not have conditional @if logic the way preprocessors like Sass or LESS do. If @if appears in a <style> block or linked stylesheet, it likely means preprocessor code was included directly in the HTML without being compiled first. The W3C validator flags this because it parses the CSS according to the official specification, where @if does not exist.
There is a CSS Conditional Rules specification, but it covers @media and @supports, not a general purpose @if. To apply styles conditionally in plain CSS, use one of those two at-rules instead.
If the intent was to check for browser support of a CSS feature, @supports is the correct replacement. If the intent was to apply styles based on viewport size or device characteristics, @media is the correct replacement.
If the code comes from a Sass or LESS source file, compile it to plain CSS before including it in HTML.
Example with the issue
<style>
@if (color: red) {
.box {
color: red;
}
}
</style>
Fixed example using @supports
<style>
@supports (color: red) {
.box {
color: red;
}
}
</style>
Fixed example using @media
<style>
@media (min-width: 768px) {
.box {
color: red;
}
}
</style>
The @media screen rule without a condition after the media type is invalid CSS syntax and will be flagged by the W3C validator.
A @media rule requires either a complete media query or a media type combined with a media feature expression. Writing @media screen alone followed by a block is actually valid CSS, so this error typically appears when the syntax inside the rule is malformed, the rule is missing curly braces, or extra characters appear between screen and the opening brace.
Common causes include:
- A missing opening or closing curly brace
{}around the media block. - Stray characters or typos between the media type and the brace.
- Placing
@mediarules inside astyleattribute (inline styles do not support at-rules). - The CSS appears inside an HTML context where the validator cannot parse it correctly, such as a malformed
<style>element.
If the intent is to target screens of a specific size, a media feature expression is required, like @media screen and (min-width: 768px). If the intent is to target all screen devices with no further conditions, @media screen is valid CSS on its own, and the real problem is likely a syntax error nearby.
Example with the issue
<style>
@media screen
.container {
width: 80%;
}
</style>
The opening curly brace for the @media block is missing, so the validator cannot recognize the rule.
Fixed example
<style>
@media screen {
.container {
width: 80%;
}
}
</style>
If the goal is to restrict styles to a specific viewport width, add a media feature:
<style>
@media screen and (min-width: 768px) {
.container {
width: 80%;
}
}
</style>
Both examples use properly matched curly braces and valid @media syntax, which resolves the validator error.
The @tailwind directive is used in Tailwind CSS source files to inject Tailwind's generated styles into your stylesheet. Common usages include @tailwind base;, @tailwind components;, and @tailwind utilities;. These directives act as placeholders that Tailwind's build tool (such as the Tailwind CLI or PostCSS plugin) replaces with actual CSS rules during compilation.
The W3C CSS Validator checks stylesheets against official CSS specifications maintained by the W3C. Since @tailwind is not defined in any CSS specification — it's a framework-specific extension — the validator correctly flags it as unrecognized. This doesn't mean your CSS is broken or will cause problems in browsers; it simply means the validator doesn't know what @tailwind is.
This distinction matters because the @tailwind directives should never appear in production CSS served to browsers. If you're seeing this validation error on a live website, it could indicate one of two things: either you're linking directly to your uncompiled Tailwind source file (which is a problem), or the validator is checking your source CSS rather than the compiled output.
How to Fix It
Ensure you're serving compiled CSS
The most important step is to make sure your build pipeline is correctly processing your Tailwind source files. The compiled output should contain only standard CSS — no @tailwind directives. Verify that your <link> tags point to the compiled CSS file, not the source file.
Use the Tailwind CDN or CLI correctly
If you're using the Tailwind CLI, run the build command to generate a compiled stylesheet and reference that file in your HTML. The compiled file will contain only valid CSS.
Consider suppressing the warning
If the validator is checking your source CSS (for example, inline <style> blocks that get processed client-side), and you're confident the directives are being handled correctly, you can safely suppress or ignore this specific warning in your validation reports.
Examples
Source CSS that triggers the warning
This is a typical Tailwind CSS source file that the validator will flag:
@tailwind base;
@tailwind components;
@tailwind utilities;
.custom-button {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
Each @tailwind line will produce an "Unrecognized at-rule" warning from the W3C Validator.
Correct approach: serve compiled CSS
After running the Tailwind build process, the output file contains only standard CSS:
*, ::before, ::after {
box-sizing: border-box;
}
.custom-button {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
.flex {
display: flex;
}
.text-center {
text-align: center;
}
Reference this compiled file in your HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<link rel="stylesheet" href="css/output.css">
</head>
<body>
<p class="text-center">Hello, world!</p>
</body>
</html>
Avoid linking to uncompiled source files
This is incorrect — it links directly to the source file containing @tailwind directives:
<!-- Don't do this -->
<link rel="stylesheet" href="css/input.css">
Instead, always link to the build output:
<!-- Do this -->
<link rel="stylesheet" href="css/output.css">
By ensuring your production site serves only the compiled CSS output, you eliminate the @tailwind at-rule warnings entirely and deliver valid, standards-compliant stylesheets to your users.
The @view-transition at-rule is valid CSS but is not yet recognized by the W3C CSS validator because it is a relatively new feature defined in the CSS View Transitions Module Level 2 specification.
The @view-transition at-rule opts a document into cross-document view transitions when navigating between two same-origin pages. It is placed in the CSS of the destination page (the page being navigated to) and accepts a navigation descriptor that controls when the transition activates.
@view-transition {
navigation: auto;
}
The navigation descriptor accepts these values:
none— no cross-document view transition occurs (default).auto— the transition activates for same-origin navigations where the navigation type is traverse, push, or replace, as long as the navigation does not include a cross-origin redirect.
Because the W3C CSS validator has not yet added support for this at-rule, the warning cannot be fixed by changing your code. The CSS itself is correct per the specification. Browser support is available in Chromium-based browsers (Chrome 126+, Edge 126+).
You can safely ignore this validator warning. If you want a completely clean validation output, move the @view-transition rule into a separate stylesheet so it does not interfere with validation of the rest of your CSS, or suppress the warning in your CI pipeline.
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