HTML Guides for custom properties
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>
The validator doesn't know the @property at-rule yet, but it is valid CSS from the CSS Properties and Values API and works in all major browsers, so you don't need to change it.
@property registers a custom property with a type. The syntax descriptor sets the kind of value it accepts, such as "<angle>" or "<length>". inherits says whether child elements inherit the value, and initial-value is the value the property has when nothing sets it. Once the browser knows the type, it can interpolate the property, so a transition or animation on it changes smoothly. To the browser, an unregistered custom property is just a sequence of tokens, and an animation on it jumps from the start value to the end value halfway through. That is why @property often shows up next to a rotating gradient, like the one in the examples below.
Chrome and Edge have supported @property since version 85, Safari since 16.4 and Firefox since 128, which made it Baseline in July 2024.
The validator skips the whole block, so it can't tell you when the rule itself is broken. syntax and inherits are required, and so is initial-value unless syntax is "*". If one of them is missing, browsers ignore the whole rule and the property behaves like an ordinary custom property.
Keep the rule and mute this message. If you want a clean report instead, move the @property rules to an external stylesheet: the validator checks the CSS in <style> elements and style attributes, but it doesn't load linked stylesheets. Wrapping the rule in @supports, @media or @layer doesn't help, because the validator still reports the @property inside it.
Example that triggers the message
<head>
<title>Rotating gradient</title>
<style>
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--angle), #4f46e5, #ec4899);
animation: spin 4s linear infinite;
}
@keyframes spin {
to {
--angle: 360deg;
}
}
</style>
</head>
Example with the rule in an external stylesheet
<head>
<title>Rotating gradient</title>
<link rel="stylesheet" href="gradient.css">
</head>
The same CSS goes into gradient.css, unchanged:
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--angle), #4f46e5, #ec4899);
animation: spin 4s linear infinite;
}
@keyframes spin {
to {
--angle: 360deg;
}
}
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>
A color written with rgb() or rgba() mixes the comma and space syntaxes, or is missing one of its values.
CSS has two ways to write rgb(), and a single function has to use one of them. The legacy syntax separates three or four values with commas, as in rgb(0, 0, 0) or rgba(0, 0, 0, 0.5). The modern syntax separates the three channels with spaces and puts the alpha after a slash, as in rgb(0 0 0 / 0.5). Values like rgb(0, 0, 0 / 0.5) and rgb(0 0, 0) combine the two, and rgb(255, 0) has only two channels. rgb() and rgba() are aliases, so either name works with either syntax.
The "X" in the message is the property that holds the color. It can be a regular property such as color or box-shadow, or a custom property such as --tw-shadow, which Tailwind CSS uses for box shadows. A browser drops a regular declaration with a broken color. A custom property keeps the value as written, and the problem shows up where var() reads it: the box-shadow that uses it becomes invalid, and the element has no shadow.
Rewrite the color in one syntax. If the value comes from a framework or from a build step such as a minifier, fix it there and rebuild, because the next build overwrites any edit to the generated file.
Invalid example
<style>
.card {
--card-shadow: 0 1px 3px rgb(0, 0, 0 / 0.1);
box-shadow: var(--card-shadow);
}
</style>
Valid example
<style>
.card {
--card-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
box-shadow: var(--card-shadow);
}
</style>
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