The CSS sign() function takes a math expression and returns 1 if the result is positive, -1 if it’s negative, 0 if it’s zero, and NaN if the calculation is invalid (i.e., there’s a math error).
:root {
--offset: 100px;
--value: 1;
}
.container {
transform: translateX(calc(var(--offset) * sign(var(--value))));
transition: transform 1s linear;
}
The following demo is editable. Try changing the --value variable value from 1 to -1 and 0 to see how the shape’s position changes in each case:
When used with the abs() function, we can use it to make calculations related to the sign of its argument.
The sign() function is defined in the CSS Values and Units Module Level 4 specification.
Syntax
<sign()> = sign( <calc-sum> )
In other words, the function returns the “sign” of some argument. By “sign” we mean that the argument indicates whether a number is positive or negative, such as “+” for positive and “−” for negative. The sign of a number helps to determine its value in calculations and comparisons.
But what exactly is <calc-sum>? Let’s look at that next.
Argument
/* It takes numbers, lengths, percentages or angles */
sign(-4) /* returns -1 */
sign(0) /* returns 0 */
sign(3rem) /* returns 1 */
sign(-60%) /* returns -1 */
/* Mathematical expressions */
sign(20% - 60%) /* returns -1 */
sign(2 * -10px) /* returns -1 */
/* Nested math functions */
sign(min(-20px, 10px)) /* returns -1 because -20px is the result */
sign(clamp(-30px, 20px, 50px)) /* returns 1 because 20px is the result */
/* returns NaN */
sign(100px / 0)
sign(calc(infinity - infinity))
sign(0 * infinity)
The sign() function takes a single argument: a <calc-sum>, which is any mathematical expression — be it a number, length, percentage, or angle) — or even a calc() expression that resolves to a number, dimension, percentage, or angle.
Basic usage
In CSS, we sometimes deal with dynamic values like those from scroll-state, offsets, or variables. The sign() function can act as a logical bridge that switches between -1, 1, and 0 (or an invalid result, NaN), allowing you to create if/else behavior without using JavaScript.
For instance, imagine you’re building a component that slides left or right based on a variable, say a tooltip. Instead of writing two different classes for each scenario, you can use sign() to determine the direction of the transform.
<div class="demo-container">
<button class="trigger" style="--side: -2;">
Hover (Left)
<span class="tooltip">I'm on the left!</span>
</button>
<!-- ... -->
<button class="trigger" style="--side: 2;">
Hover (Right)
<span class="tooltip">I'm on the right!</span>
</button>
</div>
As you can see, there --side variable can result in either a positive (2) or a negative (-2) value in this example. The sign() function evaluates the value of --side. If it’s negative, it moves the tooltip -100% (towards the left), and if it’s positive, it moves the tooltip 100% (towards the right).
.tooltip {
/* ... */
--offset: calc(100% * sign(var(--side)));
left: 50%;
transform: translate(calc(-50% + var(--offset)), -50%);
transition:
transform 0.3s,
opacity 0.3s;
}
.trigger:hover .tooltip {
opacity: 1;
/* Add an extra 10px push in the correct direction */
transform: translate(calc(-50% + var(--offset) + (10px * sign(var(--side)))), -50%);
}
That’s the sort of thing we had to reach for JavaScript to accomplish. It’s like saying, “If this is the case, then do this; otherwise do this other thing instead.” All based on the variable’s value!
Conditional styling (binary switch)
Let’s say we want to change background color or visibility based on a value. We could reach for JavaScript and use it to evaluate the value and use that to toggle classes based on the value. That’s been the traditional approach.
But now we can combine the sign() function with the clamp() function to perform that sort of binary switch. In other words, we can turn any positive number into 1 and any negative (or zero) number into 0 and apply specific styles to an element based on both conditions.
Imagine we want to bank account transactions that can display deposits (positive values) to a user’s account or withdrawals (negative values) from the account. This is the HTML we’re using for a single transaction:
<div class="transaction-card" style="--amount: 1500;">
<span class="amount">+$1,500</span>
</div>
After each transaction, there is an arrow: if it’s a deposit, we want the arrow to be green and turned upwards. But if it’s a withdrawal, then the arrow will be red and turned downwards. Using sign() and clamp(), we can differentiate between the two cases:
:root {
--is-positive: clamp(0, sign(var(--amount)), 1); /* amount can be positive (income) or negative (expense) */
--angle: calc(180deg * (1 - var(--is-positive)));
--h: calc(var(--is-positive) * 140);
}
Here, we use the --is-positive value to create a binary switch based on the --amount variable passed. If the sign amount is negative or zero, then it returns zero. Similarly, if the sign amount is positive, then it returns 1, thanks to clamp().
That way, we are able to apply a specific border-color to a .transaction-card element and set a transform on an arrow to make it face one direction of another.
:root {
--is-positive: clamp(0, sign(var(--amount)), 1);
--angle: calc(180deg * (1 - var(--is-positive)));
--h: calc(var(--is-positive) * 140);
}
.transaction-card {
border-style: solid;
border-color: hsl(var(--h), 70%, 40%); /* 0 = red hue, 140 = green hue in HSL */
border-width: 1px;
}
.transaction-card .amount::before {
content: "▲";
display: inline-block;
transform: rotate(calc(180deg * (1 - var(--is-positive))));
color: hsl(var(--h), 70%, 40%); /* 0 = red hue, 140 = green hue in HSL */
}
Notice that we’re using the hsl() color function to change the color’s “hue” value based the result of an --h variable. If the calculation of the --is-positive variable is, you know, positive, then --h will update the hue value by 140 degrees, changing the color from red (indicating a withdrawal) to green (indicating a deposit).
Similarly, if the calculation of --is-positive returns a positive value, then we rotate() the arrow character by 180 degrees, moving it from down (indicating a withdrawal) to up (indicating a deposit).
With a single switch, we have combined a style that could have been written in two classes in one instead. And again, no JavaScript required!
It works on the resolved value
The sign() function operates on a property’s resolved value rather than its raw value. This means that, depending on what property we’re working with, a positive percentage (like 10%) could resolve to a negative value depending on the that property. This can be tricky in some cases where we’d expect a -1 value and get 1 instead.
This is unlike the abs() function, which works on the raw value.
The best way to show this is on the background-position property, which, as its name says, moves a background’s position in its container. When a percentage value is used, the background moves position relative to the container’s size. Specifically, it uses the following formula to get the exact position of the background image:
offset = (container width - image width) * percentage
This means that, if we use a 1200px-wide image as the background for a 400px-wide container, we’ll get a negative value. So, when we set background-position to something like 25%, we get:
(400px - 1200px) * 25% = -200px
The resolved value here is -200px! Which is negative, and even if 25% is positive, the sign() function will work on the resolved value and return -1 instead of 1 as we might have expected.
.container {
--percent: 25%;
width: 400px;
height: 300px;
background-image: url("https://picsum.photos/id/237/1200/300"); /* 1200px wide */
background-repeat: no-repeat;
background-position-x: calc(sign(var(--percent)) * 200px);
/* results in: background-position-x: 200px; */
/* or: background-position-x: -200px; */
}
] Since the resolved value is negative (i.e., -200px), the sign() function returns -1, so 100% * -1 = -1 in this case.
Caveats
As always, when working with numbers in CSS, there are some caveats to be wary of.
sign() doesn’t have a unit
The sign() function always returns a unitless number (-1, 0, or 1). This means you cannot use it directly on properties that require a unit (like width or padding) without multiplying it by a unit first.
Wrong:
margin-left: sign(var(--x)); /* Invalid */
Right:
margin-left: calc(10px * sign(var(--x))); /* Valid */
sign() can return NaN (Not-a-Number)
If the calculation inside sign() results in NaN, the function itself returns NaN. In CSS, a property that receives NaN is typically treated as invalid, and the entire declaration is ignored.
Common triggers for NaN include dividing by zero (sign(10px / 0)) or subtracting infinity from infinity.
Edge cases
The spec defines specific behavior for edge cases:
sign(infinity)= 1sign(-infinity)=-1sign(NaN)=NaN
Specification
The sign() function is defined in the CSS Values and Units Module Level 4 specification, which is currently in Editor’s Draft. That means the information can change between now and when it officially becomes a Candidate Recommendation. Keep an eye on browser support in the meantime.