HTML Guides for calc
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 value of the --container-widget-width custom property has a math expression that combines values of incompatible types.
Elementor sets this variable on widgets placed inside a Flexbox Container. When you give a widget a custom width in its Advanced tab, Elementor writes the value into the page CSS as --container-widget-width and applies it with width: var(--container-widget-width, 100%). The "custom" unit accepts any expression, so a typo there ends up in the page unchanged.
A custom property can hold almost any value, and the validator doesn't check whether the result is a length. It does check the math inside calc(), min(), max() and clamp(), where every value you add, subtract or compare must have the same type. A unitless number next to a length breaks that rule, as in calc(100vw - 40). Browsers reject that expression too: the width that reads the variable becomes invalid at computed-value time and acts as if it were unset. The 100% fallback in var() doesn't apply, because the variable is defined.
The expression Elementor generates for its default widths, calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% ), passes the check. If you see this error, look for a custom width set on one of the widgets and add the missing unit.
Invalid example
<style>
.elementor-element-a1b2c3d {
--container-widget-width: calc(100vw - 40);
--container-widget-flex-grow: 0;
width: var(--container-widget-width, 100%);
max-width: 100%;
}
</style>
Valid example
<style>
.elementor-element-a1b2c3d {
--container-widget-width: calc(100vw - 40px);
--container-widget-flex-grow: 0;
width: var(--container-widget-width, 100%);
max-width: 100%;
}
</style>
The value of a custom property has a calc(), min(), max() or clamp() expression with a word where a number should be, such as undefined left behind by a script.
The name in the message is the custom property (any name starting with --), and the letter is the first letter of the word the validator found. Inside a math function it tries to read each operand as a number, so calc(undefined * 1px) reports "Character u", calc(10px + auto) reports "Character a", and calc(px * 2) reports "Character p". The constants pi, infinity and NaN are allowed there and don't trigger it.
The validator is stricter than browsers here. A custom property accepts almost any value, and browsers don't check the math inside it when they read the stylesheet, so they keep the declaration. The validator still checks math functions inside custom properties, although it lets a plain --button-min-width: undefined through without a message.
The value is broken all the same, and it shows where the variable is used. With min-width: var(--button-min-width), the browser substitutes calc(undefined * 1px), which isn't a valid length, so min-width behaves as if it were unset. A fallback such as var(--button-min-width, 120px) doesn't help: it only applies when the custom property isn't set at all.
The word is usually undefined, printed by a script or template that writes the style from a JavaScript variable that was never set. Fix the script so the variable always holds a number, or leave the declaration out when there is no value. If a theme, plugin or site builder generates the CSS, the fix belongs in its settings or with its developer. Inside the math, var() is accepted, so calc(var(--button-columns, 1) * 120px) is fine.
Invalid example
<button type="button" style="--button-min-width: calc(undefined * 1px);">Add to cart</button>
Valid example
<button type="button" style="--button-min-width: calc(120 * 1px);">Add to cart</button>
The value of the --global-vw custom property has a math expression that combines values of incompatible types.
The Kadence WordPress theme sets this variable on .wp-site-blocks to the viewport width minus half the scrollbar width: calc(100vw - (0.5 * var(--scrollbar-offset))). A theme script measures the scrollbar and stores its width in pixels in --scrollbar-offset. Full-width rows built with Kadence Blocks read var(--global-vw, 100vw) to extend past the content column.
A custom property can hold almost any value, so the validator doesn't check whether the result is a length. It does check the math inside calc(), min(), max() and clamp(), where the values you add or subtract must have the same type. Subtracting a unitless number from a length, as in calc(100vw - (0.5 * 15)), triggers this error. Browsers reject that expression too. Every property that reads the variable becomes invalid at computed-value time and acts as if it were unset. The 100vw fallback in var(--global-vw, 100vw) doesn't apply, because the variable is defined.
The value Kadence writes by default passes the check, because the validator doesn't type-check an expression that contains var(). If your page shows this error, something else on the site has replaced the theme's value. Look for custom CSS or a child theme that redefines --global-vw, and add the missing unit to the number.
Invalid example
<style>
.wp-site-blocks {
--global-vw: calc(100vw - (0.5 * 15));
}
</style>
Valid example
<style>
.wp-site-blocks {
--global-vw: calc(100vw - (0.5 * 15px));
}
</style>
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 max-width declaration subtracts a length from the none keyword, and a keyword can't take part in arithmetic.
none means the element has no maximum width, which leaves nothing to subtract 20 pixels from. CSS identifiers can contain hyphens and digits, so the browser reads none-20px as one unknown keyword and drops the declaration. The element then takes its max-width from an earlier rule, or has no limit at all. Adding spaces doesn't help: none - 20px is a parse error, and calc(none - 20px) fails too, because calc() only works with numeric values such as lengths and percentages.
A value like this is usually the output of a preprocessor. In Sass, $max-width - 20px compiles to none-20px when $max-width is none. To Sass, none is a string, and its - operator joins a string and another value with a hyphen instead of subtracting.
Work out which width you meant. To keep the element 20 pixels narrower than its container, use calc(100% - 20px). If you didn't want a limit, write none on its own. In a Sass mixin or function, only subtract from the variable when it holds a length, and output none unchanged otherwise.
Invalid example
<div style="max-width: none-20px;">
<p>This panel was meant to stay 20 pixels narrower than its container.</p>
</div>
Valid example
<div style="max-width: calc(100% - 20px);">
<p>This panel stays 20 pixels narrower than its container.</p>
</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>
The value of the --wp--style--global--content-size custom property has a math expression that combines values of incompatible types.
WordPress block themes create this variable from the content width in theme.json (settings.layout.contentSize), or from the layout width set in the Site Editor. WordPress copies that value into a <style> element without changing it, so the validator checks exactly what the theme or the site settings contain.
A custom property can hold almost any value, so the validator doesn't care whether the result is a length. It does check the math inside calc(), min(), max() and clamp(): when you add or subtract two values, both must have the same type. Subtracting a unitless number from a length, as in calc(720px - 40), triggers this error. Browsers reject that expression too. Every property that reads the variable through var() becomes invalid at computed-value time and behaves as if it were unset, so the content width setting stops working.
To fix the error, edit contentSize in theme.json (or the content width in the Site Editor) and add the missing unit.
Invalid example
<style>
body {
--wp--style--global--content-size: calc(720px - 40);
}
</style>
Valid example
<style>
body {
--wp--style--global--content-size: calc(720px - 40px);
}
</style>
The value of the --wp--style--global--wide-size custom property has a math expression that combines values of incompatible types.
WordPress block themes create this variable from the wide width in theme.json (settings.layout.wideSize), or from the wide width set in the Site Editor. It controls how far blocks with the "Wide width" alignment can stretch. WordPress copies the value into a <style> element as is, so the validator checks exactly what the theme or the site settings contain.
A custom property can hold almost any value, and the validator doesn't check whether the result is a length. It does check the math inside calc(), min(), max() and clamp(), where every value you add, subtract or compare must have the same type. A unitless number next to a length breaks that rule, as in calc(1200px - 40) or clamp(800px, 90vw, 1200). Browsers reject those expressions too: any property that reads the variable through var() becomes invalid at computed-value time and acts as if it were unset. Wide blocks then lose their maximum width.
To fix the error, edit wideSize in theme.json (or the wide width in the Site Editor) and add the missing unit.
Invalid example
<style>
body {
--wp--style--global--wide-size: calc(1200px - 40);
}
</style>
Valid example
<style>
body {
--wp--style--global--wide-size: calc(1200px - 40px);
}
</style>
An expression inside calc(), min(), max() or clamp() uses a value of a type that math functions can't compute with, most often the fr unit, or it combines var() with a percentage, which older versions of the CSS validator rejected by mistake.
Math functions compute with lengths, percentages, numbers, angles, times and frequencies. The validator checks the type of each operand and of the result. When one falls outside that list, it reports "Invalid type" and quotes the expression without the function name, so calc(2 * 1fr) shows up as 2 * 1fr.
The fr unit is a common cause. It's a flexible length for grid tracks, and the CSS Grid specification says fr values can't be represented in or combined with other units inside calc(). Browsers drop a declaration like grid-template-columns: calc(2 * 1fr) 1fr, and the grid falls back to the track sizes from earlier rules, or to a single column when there are none. Write the fraction directly as 2fr instead, and use minmax() when a track needs a minimum or maximum size.
When the quoted expression contains var(), as in 100% - var(--site-width) or 100% - var(--padding) * 2, the CSS is valid. A custom property has no type until the browser substitutes its value, and older versions of the CSS validator treated it as a value of the wrong type when it was combined with a percentage. The CSS validator fixed this in October 2026, and the validator Rocket Validator uses now accepts these expressions. You don't need to change your CSS. If a report from before the fix still lists the message, run a new report or mute the issue.
Invalid example
<style>
.layout {
display: grid;
grid-template-columns: calc(2 * 1fr) 1fr;
}
</style>
Valid example
<style>
.layout {
display: grid;
grid-template-columns: 2fr 1fr;
}
</style>
Valid var() expressions
These declarations are valid CSS and pass the current validator:
<style>
:root {
--header-height: 64px;
--padding: 16px;
--offset-x: 20px;
}
.content {
width: calc(100% - var(--padding) * 2);
height: calc(100% - var(--header-height));
background-position: calc(50% + var(--offset-x)) center;
}
</style>
CSS math functions like calc(), min(), max(), and clamp() follow strict rules about how operands and operators interact. The error "one operand must be a number" most commonly fires in two scenarios: either an operand is missing entirely (e.g., calc(100% - )), or both operands in a multiplication or division carry units (e.g., calc(10px * 5px)). The CSS Values and Units specification requires that for * (multiplication), at least one side must be a unitless <number>. For / (division), the right-hand side must always be a unitless <number>. You cannot multiply two lengths together or divide a length by another length within calc().
This matters for several reasons. Browsers will discard the entire property declaration if the calc() expression is invalid, which can cause layout breakage or fallback to unexpected default values. The W3C validator catches these errors in inline style attributes and embedded <style> blocks, helping you identify expressions that will silently fail in production. Fixing these issues ensures predictable rendering across all browsers.
How the rules work
- Addition and subtraction (
+,-): Both operands must have compatible types (e.g., both lengths, or both percentages, or a mix of length and percentage). Both must be present. - Multiplication (
*): At least one operand must be a plain<number>(unitless). You can writecalc(10px * 3)orcalc(3 * 10px), but notcalc(10px * 5px). - Division (
/): The right-hand operand must be a plain<number>(unitless and non-zero). You can writecalc(100px / 2), but notcalc(100px / 2px).
Examples
Missing operand
A common mistake is leaving out a value on one side of an operator:
<!-- ❌ Wrong: missing operand after the minus sign -->
<div style="width: calc(100% - );"></div>
<!-- ✅ Fixed: both operands are present -->
<div style="width: calc(100% - 50px);"></div>
Multiplying two values with units
You cannot multiply two unit-bearing values together, because the result would be a meaningless type like "px²":
<!-- ❌ Wrong: both operands have units -->
<div style="width: calc(10px * 5px);"></div>
<!-- ✅ Fixed: one operand is a unitless number -->
<div style="width: calc(10px * 5);"></div>
Dividing by a value with units
The divisor in a / operation must be a unitless number:
<!-- ❌ Wrong: dividing by a value with units -->
<div style="height: calc(500px / 2em);"></div>
<!-- ✅ Fixed: divisor is a unitless number -->
<div style="height: calc(500px / 2);"></div>
Nested calc() with a missing value
Errors can hide inside nested expressions:
<!-- ❌ Wrong: inner calc has an incomplete expression -->
<p style="margin-top: calc(2rem + calc(100% * ));"></p>
<!-- ✅ Fixed: all operands are present and valid -->
<p style="margin-top: calc(2rem + calc(100% * 0.5));"></p>
Using variables or keywords where a number is expected
Sometimes a typo or misunderstanding leads to a non-numeric token where a number is required:
<!-- ❌ Wrong: "auto" is not a valid operand in calc() -->
<div style="width: calc(auto * 2);"></div>
<!-- ✅ Fixed: use a numeric value or percentage -->
<div style="width: calc(100% * 2);"></div>
To resolve this error, review every calc(), min(), max(), and clamp() expression in your inline styles and stylesheets. Confirm that all operators have valid operands on both sides, that * always has at least one unitless number, and that / always has a unitless number on the right. If you're building expressions dynamically (e.g., via JavaScript or a templating engine), double-check that variables are being interpolated correctly and not producing empty or invalid values.
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