HTML Guides for units
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.
A position in the background shorthand is a plain number like 10, and CSS only accepts a number without a unit when it is 0.
In background: url(banner.png) no-repeat 10 20, the two numbers set background-position, which takes lengths such as 10px or 1.5em, percentages, or keywords like left and center. CSS has no default unit, so 10 alone is not a length. Browsers ignore the whole declaration, and the element loses its background color and image along with the position.
The bare number often comes from a template or CMS setting that prints an offset without px, or from a value copied out of a design tool. Add the unit you meant, or switch to a percentage or keyword. 0 is the one number that is valid on its own, because zero is the same length in every unit.
The validator words two close cases differently. A unitless size after the slash, as in center / 100 auto, is reported as a "unit" error instead of a "length" one. A unitless number that comes second, after a valid first position such as 10px 20, is reported as not being a background value. Adding the unit fixes both.
Invalid example
<div style="background: url(banner.png) no-repeat 10 20;">
<p>Summer sale</p>
</div>
Valid example
<div style="background: url(banner.png) no-repeat 10px 20px;">
<p>Summer sale</p>
</div>
A math function in the inset-inline-start value, such as calc(), min(), max() or clamp(), combines values whose types can't be mixed, like a length and a unitless number.
inset-inline-start sets how far a positioned element sits from the start edge of its containing block in the inline direction. In left-to-right text that is the left edge, and in right-to-left text it is the right edge. The property takes a length such as 12px or 1rem, a percentage, or auto, so any math function inside it has to produce a length or a percentage too.
When calc() adds or subtracts, both sides must have the same type. calc(1rem + 2px) adds two lengths, but calc(1rem + 2) tries to add a plain number to a length, and the result has no type the browser can use. The arguments of min(), max() and clamp() follow the same rule, so min(10px, 5) can't compare a length with a number. Mixing in another type, as in calc(10deg + 2px), triggers the same error.
The usual cause is a number that lost its unit, often after a hand edit or a preprocessor variable that was declared without one. Multiplication and division work differently: one side may be a plain number that scales the other, so calc(4 * 1rem + 2px) is valid.
Browsers ignore a declaration with a mistyped expression. The element keeps the inset-inline-start it got from earlier rules, usually auto, and ends up in the wrong place.
Invalid example
<div style="position: absolute; inset-inline-start: calc(1rem + 2);">Badge</div>
<div style="position: absolute; inset-inline-start: min(10px, 5);">Tooltip</div>
Valid example
<div style="position: absolute; inset-inline-start: calc(1rem + 2px);">Badge</div>
<div style="position: absolute; inset-inline-start: min(10px, 5vw);">Tooltip</div>
A calc() expression in the left property contains a word starting with "n", such as none or NaN, where a number or a length should be.
Inside calc() and the other math functions (min(), max(), clamp()), each operand must be a number, a length, a percentage, or a var() reference. When the validator finds a word instead, it tries to read it as a number and reports the word's first letter. calc(50% - none) reports "Character n", and calc(50% - NaNpx) reports "Character N". Words starting with other letters give the same message with that letter, such as "Character a" for auto.
NaN usually comes from a script or template that computes the position. If the calculation divides by zero or reads a value that isn't a number, calc(50% - {{ offset }}px) comes out as calc(50% - NaNpx). Keywords such as none or auto are valid on their own (left: auto) but not as operands of calc(). Browsers ignore the whole declaration, so the element gets the left value it would have without it.
To fix it, put a length in place of the word, or make sure the template variable always holds a number. If the value comes from a custom property, var() is fine inside calc(), and a fallback covers the case where the property isn't set: calc(50% - var(--offset, 0px)).
Outside calc(), other mistakes in left give different messages: left: 10 says only 0 can be written without a unit, and left: NaNpx says the value is not a left value.
Invalid example
<div class="tooltip" style="position: absolute; left: calc(50% - NaNpx);">Copied</div>
Valid example
<div class="tooltip" style="position: absolute; left: calc(50% - 120px);">Copied</div>
A math function in the min-height value, such as calc(), min(), max() or clamp(), combines values whose types can't be mixed, like a length and a unitless number.
min-height sets the smallest height an element can have, even when its content would make it shorter. The property takes a length such as 300px or 50vh, a percentage, or a keyword like auto, so any math function inside it has to produce a length or a percentage too.
When calc() adds or subtracts, both sides must have the same type. calc(100vh - 80px) subtracts one length from another, but calc(100vh - 80) subtracts a plain number from a length, and the result has no type the browser can use. The arguments of min(), max() and clamp() follow the same rule, so max(300px, 50) can't compare a length with a number.
This mistake often turns up in a full-screen section that subtracts a fixed header from the viewport height, with the unit left off the header's height. Multiplication is different: one side can be a plain number that scales the other, so calc(100vh - 2 * 40px) is valid.
Browsers ignore a declaration with a mistyped expression. The element keeps the min-height it got from earlier rules, usually auto, and the section can end up shorter than you intended.
The validator doesn't look inside var(), so a custom property that holds a bare number passes validation. The browser still rejects the result once it substitutes the value.
Invalid example
<div style="min-height: calc(100vh - 80);">Hero</div>
<div style="min-height: max(300px, 50);">Card</div>
Valid example
<div style="min-height: calc(100vh - 80px);">Hero</div>
<div style="min-height: max(300px, 50vh);">Card</div>
The min-width value uses a math function such as calc(), min(), max() or clamp() to combine values whose types don't match, most often a length and a number with no unit.
min-width keeps an element from getting narrower than a given size, even when its container shrinks or its width asks for less. It accepts a length like 320px or 20rem, a percentage, or a keyword such as auto or min-content, so any math function in it has to work out to a length or a percentage.
Addition and subtraction inside calc() only work between values of the same type. calc(100vw - 300px) takes one length away from another, while calc(100vw - 300) takes a bare number away from a length, which CSS can't compute. The comparison functions apply the same rule to their arguments: max(320px, 50) asks for the larger of a length and a number, and clamp(200, 50%, 400px) sets a lower bound with no unit.
A common source is a layout that subtracts the width of a sidebar or fixed column from the viewport, with the unit left off that width. Multiplying or dividing by a plain number is fine, because the number only scales the other value: calc(100vw - 2 * 150px) is valid.
Browsers drop a declaration whose expression has the wrong type. The element then uses the min-width from earlier rules, or the initial auto, and the column can shrink below the size you planned when the screen is narrow.
The validator only reports this error when one of the values is a length. It lets calc(100% - 300) and max(50%, 10) through, but browsers reject those as well, so check percentage expressions for a missing unit yourself. It also skips values that come from var().
Invalid example
<style>
.content {
min-width: calc(100vw - 300);
}
.card {
min-width: max(320px, 50);
}
</style>
Valid example
<style>
.content {
min-width: calc(100vw - 300px);
}
.card {
min-width: max(320px, 50%);
}
</style>
A math function in the padding value, such as calc(), min(), max() or clamp(), combines values whose types don't match, most often a length and a number with no unit.
padding is a shorthand that takes one to four values, for the top, right, bottom and left sides. Each value is a length like 16px or 1rem, or a percentage, so a math function in any of those positions has to work out to a length or a percentage. The validator checks every position, and 1rem calc(1rem + 2) fails just like calc(1rem + 2) on its own. The message only names padding, so it won't tell you which side has the problem. Check each value in the declaration.
Addition and subtraction inside calc() only work between values of the same type. calc(1rem + 2px) adds two lengths, while calc(1rem + 2) adds a bare number to a length, which CSS can't compute. min(), max() and clamp() apply the same rule to their arguments, so max(16px, 2) and clamp(1rem, 5vw, 3) each mix a length with a number. Multiplying or dividing by a plain number is fine, because the number only scales the other value: calc(2 * 1rem) is valid.
A common source is side padding worked out from a fixed content width, such as calc((100vw - 1200) / 2), with the unit left off the width. Writing 1200px fixes it.
Browsers drop a declaration whose expression has the wrong type. Because padding sets all four sides at once, a mistake in one value throws away the other three as well. The element keeps the padding from earlier rules, or the initial 0.
The validator doesn't catch every case. It lets calc(5% + 2) and max(5%, 2) through, although browsers reject both. It also accepts calc(1rem + 0), but inside a math function a bare 0 counts as a number, so browsers drop that one too. Expressions that use var() pass whatever the variable holds.
Invalid example
<style>
.section {
padding: calc(1rem + 2) 1rem;
}
.card {
padding: 1rem 1.5rem max(16px, 2);
}
</style>
Valid example
<style>
.section {
padding: calc(1rem + 2px) 1rem;
}
.card {
padding: 1rem 1.5rem max(16px, 2vw);
}
</style>
A calc() expression in padding-top, padding-right, padding-bottom or padding-left has a unit with no number in front of it, such as px where 24px was meant.
Inside calc(), the validator expects a number in front of every unit. When it finds a letter where the number should be, it names that letter in the message: calc(px + 1rem) reports "Character p", and calc(rem + 4px) reports "Character r". Outside calc(), a bare px gets a different message, which says px is not a valid padding-top value.
The usual cause is a template or build step that writes a number from a variable into the CSS. If the variable is empty, calc({{ spacing }}px + 1rem) comes out as calc(px + 1rem). Browsers discard the whole declaration, so the element keeps the padding it would have without that rule.
To fix it, put the number back in front of the unit, or make sure the variable always has a value. When the value comes from a custom property, give var() a fallback, as in calc(var(--banner-space, 0px) + 1rem). If the value can be zero, write 0px and not 0: inside calc() a unitless 0 is a number, and a number can't be added to a length.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Banner</title>
<style>
.banner {
padding-top: calc(px + 1rem);
padding-bottom: calc(px + 1rem);
}
</style>
</head>
<body>
<div class="banner">Welcome</div>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Banner</title>
<style>
.banner {
padding-top: calc(24px + 1rem);
padding-bottom: calc(24px + 1rem);
}
</style>
</head>
<body>
<div class="banner">Welcome</div>
</body>
</html>
A math function inside the transform value, such as calc(), min(), max() or clamp(), combines values whose types can't be mixed, like a unitless number and an angle.
Every CSS value has a type. 10px and 2em are lengths, 45deg and 0.5turn are angles, 2s is a time, and 90 with no unit is a plain number. When calc() adds or subtracts, both sides must have the same type, so calc(90deg - 45deg) is a valid angle while calc(90 - 45deg) asks the browser to subtract an angle from a number. The arguments of min(), max() and clamp() follow the same rule: min(50px, 10deg) can't compare a length with an angle.
The usual cause is a missing unit on one of the numbers, or a value copied from another function, such as a length pasted into rotate(). Multiplication and division work differently, because one side may be a plain number that scales the other. To turn a unitless number into an angle or a length, multiply it by one unit, as in calc(90 * 1deg - 45deg).
Browsers treat a transform declaration with a mistyped expression as invalid and ignore it. That includes the functions in the same list that were written correctly, so the element isn't transformed at all.
Invalid example
<div style="transform: rotate(calc(90 - 45deg));">Tilted label</div>
<div style="transform: translateX(min(50px, 10deg));">Sliding panel</div>
Valid example
<div style="transform: rotate(calc(90deg - 45deg));">Tilted label</div>
<div style="transform: translateX(min(50px, 10vw));">Sliding panel</div>
In CSS, most numeric values represent a length, time, angle, or other dimension, and the unit tells the browser how to interpret the number. Writing padding: 20 is ambiguous — does it mean 20 pixels, 20 ems, or 20 percent? Without a unit, the browser has no way to determine the author's intent, so it treats the value as invalid and discards the entire declaration. This can lead to broken layouts, missing spacing, or other visual problems that are difficult to debug.
The only exception to this rule is 0. Zero pixels is the same as zero ems, zero rems, or zero percent — it's always nothing regardless of the unit. Because of this, the CSS specification allows 0 to be written without a unit. In fact, omitting the unit on zero values is considered a best practice for cleaner, more concise CSS.
This validation error commonly appears in inline style attributes, which is where the W3C HTML Validator encounters it. However, the same rule applies to CSS written in <style> elements and external stylesheets.
Common causes of this error include:
- Accidentally forgetting to type the unit after a number.
- Copy-pasting values from design tools that don't include units.
- Confusing CSS with JavaScript's
element.style.width = "50"pattern (where some APIs accept unitless numbers). - Assuming that
pxis the default unit (it is not — there is no default unit in CSS).
Examples
Incorrect — missing units on numeric values
<div style="margin: 10; padding: 20; width: 300;">
Content here
</div>
The validator will report errors for 10, 20, and 300 because none of them have units.
Correct — units specified on all non-zero values
<div style="margin: 10px; padding: 20px; width: 300px;">
Content here
</div>
Correct — zero without a unit
<div style="margin: 0; padding: 0; border: 0;">
No units needed for zero
</div>
Correct — mixing zero and non-zero values
<div style="margin: 0 10px 0 20px;">
Top and bottom margins are zero, sides have units
</div>
Correct — using other unit types
<p style="font-size: 1.2em; line-height: 1.5em; margin-bottom: 2rem;">
Text with relative units
</p>
Note that line-height is a special case in CSS — it actually accepts a unitless number (like 1.5) as a valid value, where the number acts as a multiplier of the element's font size. This is not the same as a missing unit; it's a deliberately unitless ratio defined in the specification. Most other properties do not have this behavior.
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.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries