abs()

Gabriel Shoyombo on

The abs() function takes in an argument and returns its absolute value, i.e., it always returns the argument’s positive value, keeping the same type as its input (but with edge cases that we’ll cover).

div {
  padding: abs(20px - 10px); /* returns 10px */
  width: abs(-200px); /* becomes 200px */
}

The abs() function is defined in the CSS Values and Units Module Level 4 specification.

Syntax

<abs()> = abs( <calc-sum> )

In other words, the abs() function returns the absolute value of its argument. What exactly is its argument? Let’s look at that next.

Arguments

/* Plain Numbers */
abs(-4); /* returns 4 */
abs(16); /* returns 16 */

/* Lengths */
abs(-11px); /* returns 11px */
abs(3rem); /* returns 3rem */
abs(-8vh); /* returns 8vh */

/* Percentage */
abs(-60%); /* returns 60% */
abs(30%); /* returns 30% */

/* calc expressions */
abs(20% - 60%); /* returns 40% */
abs(-4rem + 1rem); /* returns 3rem */
abs(2 * -10px); /* returns 20px */

/* math function */
abs(min(-20px, 10px)); /* returns 10px  */
abs(max(-50%, -10%));  /* returns 10%   */
abs(clamp(-30px, -2rem, 0px)); /* returns 20px */

The abs() function accepts a single argument:

  • <calc-sum>: Any number, length, percentage, angle, or expression used in calc() that resolves to a numeric value or dimension, and even other math functions (e.g., -20px50%min(100%, 20px)). Lastly, units are preserved: if you pass px, you get px back; if you pass %, you get % back, and so on.

Basic usage

The abs() functions always returns the positive value from any value that can have a sign.

To show this, imagine we set a negative margin-top to pull an element up, say -30px, so the content moves up with it. However, this could end up moving the content behind the element above it.

.card {
  margin-top: -30px;
  position: relative;
  z-index: 0; /* Sits below header */
}

You may ask yourself: “Why would we set margin-top: -30px when you know it’s going to cover the content?” Well, it could come from a variable used in multiple scenarios, and here, it returns a negative value. For example, let’s say that the value comes from an existing CSS variable:

:root {
  --overlap: -30px;
}
.card {
  margin-top: var(--overlap);
  position: relative;
  z-index: 0; /* Sits below header */
}

We could offset it by setting padding-top: 30px. While this could serve as a temporary fix, it becomes a little more unmanageable if we change it somewhere else in the code.

.old-way {
  padding-top: 30px;
}

With abs(), we can declare any positive or negative variable and use it anywhere without having to worry about changes or layout breaks. It’s like telling the browser that, however much you pull this box up, push the content down by the same amount. It’s reversing what’s negative into a positive value.

.new-way {
  padding-top: abs(var(--overlap));
}

Creating a wavy effect with abs()

In this example, we’re creating a row of bars and applying a wavy animation for each bar’s height and opacity. The markup would be the next:

<div class="wave-container">
  <div class="bars-wrapper" style="--center-idx: 6;">
    <div class="bar" style="--my-idx: 1"></div>
      <div class="bar" style="--my-idx: 2"></div>
      <div class="bar" style="--my-idx: 3"></div>
      <div class="bar" style="--my-idx: 4"></div>
      <div class="bar" style="--my-idx: 5"></div>
      <div class="bar center-marker" style="--my-idx: 6"></div>
      <div class="bar" style="--my-idx: 7"></div>
      <div class="bar" style="--my-idx: 8"></div>
      <div class="bar" style="--my-idx: 9"></div>
      <div class="bar" style="--my-idx: 10"></div>
      <div class="bar" style="--my-idx: 11"></div>
    </div>
  </div>
</div>

You’ll notice each element has a hardcoded index (e.g., style="--my-idx: 1"), and that we also set aside the total number of elements. However, you can skip this step if your browser supports the sibling-index() and sibling-count() functions.

To make the animation wavy, there must be an animation-delay which slows down the rise and fall of each bar.

.bar {
  --distance: abs(var(--my-idx) - var(--center-idx));
  animation-delay: calc(var(--distance) * 0.1s);
}

In this example, the --my-idx variables increases by 1 on each bar in the markup, and that means the center bar’s --my-idx value will be greater than those before it, resulting in a negative distance value. This would be a big problem since time can’t be negative!

We solved this using the abs() function by keeping the result of var(--my-idx) - var(--center-idx) positive, and passing it to the calc() function to calculate a positive animation-delay.

Finally, we implement the wave-motion animation @keyframes and apply it to each bar.

.bar {
  animation-name: wave-motion;
  animation-duration: 1.5s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

/* The animation keyframes */
@keyframes wave-motion {
  0%,
  100% {
    height: 40px;
    opacity: 0.5;
  }
  50% {
    /* The peak of the wave */
    height: 120px;
    opacity: 1;
    filter: brightness(1.5);
    box-shadow: 0 0 20px var(--color);
  }
}

Now we have a beautiful wave animation, which we would otherwise have had to write complex nth-child selectors or use JavaScript to apply delays to create a symmetrical effect manually.

abs() operates at the expression level, not after property resolution

You might wonder how abs() interacts with properties that have special resolution rules. For example, background-position uses a formula to convert percentages into lengths:

offset = (container size - image size) × percentage

When a background image is larger than its container, this formula can produce a negative length even though the percentage itself (e.g., 25%) is positive.

One might expect that abs(25%) in a background-position context would behave differently from plain 25%, returning a positive length instead of a negative one. However, testing shows they behave identically:

.container {
  background-position-x: 25%; /* Resolves based on image/container sizes */
  background-position-x: abs(25%); /* Behaves the same */
}

Upon testing, both of these expressions are the same! Meaning that, abs() uses the raw value, at least in current browser implementations.

This is because abs() operates on the mathematical expression itself, not on the value after the property’s own resolution logic has been applied. The percentage 25% is not a negative number — it’s simply 25%. The abs() function evaluates it as such and returns 25%. It’s only after that result is passed to background-position-x that the property’s special sizing formula kicks in.

In other words: abs() doesn’t retroactively modify a property’s computed value; it evaluates its argument as a pure mathematical expression and returns the result.

abs() doesn’t work with keywords

Math functions in CSS cannot work directly with intrinsic values. So while auto or max-content represent values to the browser engine; they are not numbers to the math engine.

width: abs(auto); /* 👎 Invalid */
flex-basis: abs(content); /* 👎  Invalid */

If you need to work with intrinsic values and math, consider using calc-size(). For example, if you wanted to reduce an intrinsic size by a fixed amount:

width: abs(auto - 20px); /* 👎 Invalid */
width: calc-size(auto - 20px); /* 👍 Valid */

On that note, calc-size() is designed specifically to work with intrinsic sizing keywords like autocontent, etc., whereas abs()works with numeric dimensions.

abs() can be nested in calc()

The abs() function can be nested inside calc():

width: calc(100% - abs(-20px));

Edge cases

The spec defines specific behavior for edge cases:

  • abs(infinity) = infinity
  • abs(-infinity) = infinity
  • abs(NaN) = NaN
  • abs(-0) = 0 (unitless); abs(0px) = 0px (with unit)

Specification

The abs() 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.

Browser support

As of January 2025, this is widely supported in modern browsers — except Samsung Internet — and is stable enough for production use, though the spec may still evolve.