Skip to content

Not recognizing calc result as number, nor recognizing @property #518

Description

@nickchomey

Below is an excerpt of css being implemented in a WordPress mechanism to generate css-only Low Quality Image Placeholders (https://github.com/WordPress/performance/pull/2550/changes), inspired by this article https://leanrada.com/notes/css-only-lqip/

@property --lqip {
	syntax: "<number>";
	inherits: true;
	initial-value: 0;
}

[style*="--lqip:"] {
	--lqip-ca: mod(round(down, calc((var(--lqip) + 524288) / 262144)), 4);
	--lqip-cb: mod(round(down, calc((var(--lqip) + 524288) / 65536)), 4);
	--lqip-cc: mod(round(down, calc((var(--lqip) + 524288) / 16384)), 4);
	--lqip-cd: mod(round(down, calc((var(--lqip) + 524288) / 4096)), 4);
	--lqip-ce: mod(round(down, calc((var(--lqip) + 524288) / 1024)), 4);
	--lqip-cf: mod(round(down, calc((var(--lqip) + 524288) / 256)), 4);
	--lqip-ll: mod(round(down, calc((var(--lqip) + 524288) / 64)), 4);
	--lqip-aaa: mod(round(down, calc((var(--lqip) + 524288) / 8)), 8);
}

W3C CSS Validator shows these errors


1		Unrecognized at-rule @property
8	[style*="--lqip:"]	The types are incompatible ), 4)
9	[style*="--lqip:"]	The types are incompatible ), 4)
10	[style*="--lqip:"]	The types are incompatible ), 4)
11	[style*="--lqip:"]	The types are incompatible ), 4)
12	[style*="--lqip:"]	The types are incompatible ), 4)
13	[style*="--lqip:"]	The types are incompatible ), 4)
14	[style*="--lqip:"]	The types are incompatible ), 4)
15	[style*="--lqip:"]	The types are incompatible ), 8)
15	[style*="--lqip:"]	Parse Error

As noted in a code review comment there, this is what Claude Opus 5 said

Both tools are wrong, and about the same thing: the two‑argument round(down, A) form where the rounding interval B is omitted.

What the spec says

css-values-4 §10.3:

The round(<rounding-strategy>?, A, B?) function … If the type of A matches <number>, then B may be omitted, and defaults to 1. … In all other cases, omitting B is invalid.

--lqip is registered as syntax: "<number>" (lqip.css:12-16), so A is a <number> and omitting B is legal. The code is correct.

Why the error points at mod() instead of round()

Neither tool can establish a type for the round() result, so it falls through to mod()'s "both arguments must be the same type" check against the literal 4/8. That's also why lqip.css:27mod(calc(var(--lqip) + 524288), 8), no round() — is clean in both tools while lines 19‑26 are not.

The two tools fail for different reasons

I probed both with variants:

Expression PhpStorm W3C
round(down, 5) ok error
round(down, var(--lqip)) ok
round(down, calc(var(--lqip) + 1)) error error
round(down, …, 1) (explicit interval) ok ok
  • PhpStorm does implement the number default — it even reads your @property to type a bare var(--lqip). It just loses the type when the argument is a calc() containing a var(). Dropping the redundant calc() wrapper (round(down, (var(--lqip) + 524288) / 262144)) doesn't help.
  • W3C/Jigsaw doesn't implement the omitted-B rule at all — even round(down, 5) reports "The types are incompatible."

Browsers disagree with both

In Chrome 152, with --lqip: 192900, the omitted-interval and explicit-interval forms both compute --lqip-ca: 2, and CSS.supports('opacity', 'round(down, 0.5)') is true.

If you want the warnings gone

Adding the explicit , 1 to the eight round() calls is a semantic no-op that satisfies both tools. Verified on a copy: W3C goes from 9 errors to 1, PhpStorm to 0.

--lqip-ca: mod(round(down, calc((var(--lqip) + 524288) / 262144), 1), 4);

The remaining W3C error is Unrecognized at-rule "@property" at lqip.css:12 — a separate Jigsaw gap (it predates css-properties-values-api), and nothing you can or should work around.

So, there's two issues:

  1. not recognizing the calc as number
  2. Not recognizing @Property

This resolves the calc/round error, but shouldn't be necessary (and isn't, in actual browsers)

	--lqip-ca: mod(round(down, calc((var(--lqip) + 524288) / 262144), 1), 4);
	--lqip-cb: mod(round(down, calc((var(--lqip) + 524288) / 65536), 1), 4);
	--lqip-cc: mod(round(down, calc((var(--lqip) + 524288) / 16384), 1), 4);
	--lqip-cd: mod(round(down, calc((var(--lqip) + 524288) / 4096), 1), 4);
	--lqip-ce: mod(round(down, calc((var(--lqip) + 524288) / 1024), 1), 4);
	--lqip-cf: mod(round(down, calc((var(--lqip) + 524288) / 256), 1), 4);
	--lqip-ll: mod(round(down, calc((var(--lqip) + 524288) / 64), 1), 4);
	--lqip-aaa: mod(round(down, calc((var(--lqip) + 524288) / 8), 1), 8);

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions