Skip to content

[css-color-5] [css-values] Using color components in trigonometric functions in relative color syntax yield different results in different browsers #13938

Description

@alinnert

Problem

If you use a color component value (like h, s, or l) inside a trigonometric function (like sin() or cos()) inside the relative color syntax the result differs in every major browser.

I found there seem to be no WPT tests that check for this specific combination of CSS features.

Example

.element {
  background: hsl(from hsl(0 50 50) h s calc((sin(l) + 1) * 50));
}

I've also created a JSFiddle to demonstrate the issue.

What the specs say

The spec of the relative color syntax says:

The component keywords return a <number>, or none; if they were originally specified as a <percentage> or an <angle>, that <percentage> is resolved to a <number> and the <angle> is resolved to a <number> of degrees (which is the canonical unit) in the range [0, 360].

The spec of the Trigonometric Functions says:

The sin(A), cos(A), and tan(A) functions all contain a single calculation which must resolve to either a <number> or an <angle>, and compute their corresponding function by interpreting the result of their argument as radians. (That is, sin(45deg), sin(.125turn), and sin(3.14159 / 4) all represent the same value, approximately .707.)

Expected behavior

A lightness value of 50 (or 50%) in the origin-color should resolve to the <number> 50. This means that sin(l) and sin(50) should be equivalent. In both cases it should be interpreted as sin(50rad) which yields the value -0.2623748537. By adding 1 and multiplying the value with 100 you get a value between 0 and 100 - in this case 36.8812573148. That means the final color should be hsl(0 50 36.8812573148).

Actual behavior

Safari 26.5

Safari actually produces the correct color.

Safari screenshot Image

Chrome 148.0.7778.168

Chrome produces a different color: hsl(0 49.8 88.3)

After trying a few different values it seems that Chrome interprets 50% as 50deg when used in sin(). So, it calculates sin(50deg) instead of sin(50rad). I think so because:

  • A lightness value of 0 in the origin-color produces a color with medium brightness
  • A value of 90 produces the lightest color
  • A value of 180 produces the same color as the value 0
  • A value of 270 produces the darkest color
  • A value of 360 produces the same color as the value 0

I've checked all values from 0 to 360 in steps of 10 to be sure.

Chrome screenshot Image

Firefox 150.0.3

Firefox cannot calculate any color doing that. So, some form of internal error seems to occur. But the exact result depends on what the origin-color actually is.

Assuming I'm using a fallback color like this:

.element {
  background: gray;
  background: hsl(from hsl(0 50 50) h s calc((sin(l) + 1) * 50));
}
  • If the origin-color is currentColor (hsl(from currentColor ...)) or a color function (like above) then the fallback color gets used.
  • If the origin-color is a custom property however (hsl(from var(--some-color) ...)) no color is used at all and the element gets rendered with a transparent background, ignoring all background rules all together.
Firefox screenshot
  • The orange box (with the diagonal line from bottom left to top right) represents the case where the fallback color gets ignored and the target element gets rendered with a transparent background.
  • The red boxes (with the diagonal line from top left to bottom right) represent the case where the fallback color gets used.
Image

What was I trying to do?

I was trying to make any color darker in a way so that light colors get way more dark than colors that are already somewhat dark. That's why I came up with this formula:

.element {
  background: hsl(from currentColor h s calc((sin((l * pi) / 200) * 100) / 2));
}

If it worked correctly it would result in every color having a lightness value between 0 and 50 while dark colors would be brighter than they would be with calc(l / 2).

Here's a graph to visualize that:

Show graph
  • The black, straight graph represents calc(l / 2)
  • The red, curved graph represents calc((sin((l * pi) / 200) * 100) / 2))
Image

Next steps, questions

I wanted to make sure that my version of "expected behavior" is correct. If that's the case I try to open issues on Firefox' and Chrome's issue tracker as well as create WPT tests.

EDIT 2026-05-20

I've created an issue on the Chromium issue tracker:
https://issues.chromium.org/issues/515079560

I've found an already existing issue about this on the Firefox issue tracker:
https://bugzilla.mozilla.org/show_bug.cgi?id=1951206

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions