This is a series! It all started a couple of articles ago, when we found out that, according to the State of CSS 2025 survey, trigonometric functions were the “Most Hated” CSS feature.

I’ve been trying to change that perspective, so I showcased several uses for trigonometric functions in CSS: one for sin() and cos() and another on tan(). However, that’s only half of what trigonometric functions can do. So today, we’ll poke at the inverse world of trigonometric functions: asin(), acos(), atan() and atan2().
CSS Trigonometric Functions: The “Most Hated” CSS Feature
sin()andcos()tan()asin(),acos(),atan()andatan2()(You are here!)
Inverse functions?
Recapping things a bit, given an angle, the sin(), cos() and tan() functions return a ratio presenting the sine, cosine, and tangent of that angle, respectively. And if you read the last two parts of the series, then you already know what each of those quantities represents.
What if we wanted to go the other way around? If we have a ratio that represents the sine, cosine or tangent of an angle, how can we get the original angle? This is where inverse trigonometric functions come in! Each inverse function asks what the necessary angle is to get a given value for a specific trigonometric function; in other words, it undoes the original trigonometric function. So…
acos()is the inverse ofcos(),asin()is the inverse ofsin(), andatan()andatan2()are the inverse oftan().
They are also called “arcus” functions and written as arcos(), arcsin() and arctan() in most places. This is because, in a circle, each angle corresponds to an arc in the circumference.
The length of this arc is the angle times the circle’s radius. Since trigonometric functions live in a unit circle, where the radius is equal to 1, the arc length is also the angle, expressed in radians.
Their mathy definitions are a little boring, to say the least, but they are straightforward:
y = acos(x)such thatx = cos(y)y = asin(x)such thatx = sin(y)y = atan(x)such thatx = tan(y)
acos() and asin()
Using acos() and asin(), we can undo cos(θ) and sin(θ) to get the starting angle, θ. However, if we try to graph them, we’ll notice something odd:

The functions are only defined from -1 to 1!
Remember, cos() and sin() can take any angle, but they will always return a number between -1 and 1. For example, both cos(90°) and cos(270°) (not to mention others) return 0, so which value should acos(0) return? To answer this, both acos() and asin() have their domain (their input) and range (their output) restricted:
acos()can only take numbers between-1and1and return angles between0°and180°.asin()can only take numbers between-1and1and return angles between-90°and90°.
This limits a lot of the situations where we can use acos() and asin(), since something like asin(1.2) doesn’t work in CSS* — according to the spec, going outside acos() and asin() domain returns NaN — which leads us to our next inverse function…
atan() and atan2()
Similarly, using atan(), we can undo tan(θ) to get θ. But, unlike asin() and acos(), if we graph it, we’ll notice a big difference:

This time it is defined on the whole number line! This makes sense since tan() can return any number between -Infinity and Infinity, so atan() is defined in that domain.
atan() can take any number between -Infinity and Infinity and returns angles -90° and 90°.
This makes atan() incredibly useful to find angles in all kinds of situations, and a lot more versatile than acos() and asin(). That’s why we’ll be using it, along atan2(), going forward. Although don’t worry about atan2() for now, we’ll get to it later.
Finding the perfect angle
In the last article, we worked a lot with triangles. Specifically, we used the tan() function to find one of the sides of a right-angled triangle from the following relationships:

To make it work, we needed to know one of its sides and the angle, and by solving the equation, we would get the other side. However, in most cases, we do know the lengths of the triangle’s sides and what we are actually looking for is the angle. In that case, the last equation becomes:

Triangles and Conic Gradients
Finding the angle comes in handy in lots of cases, like in gradients, for instance. In a linear gradient, for example, if we want it to go from corner to corner, we’ll have to match the gradient’s angle depending on the element’s dimensions. Otherwise, with a fixed angle, the gradient won’t change if the element gets resized:
.gradient
background: repeating-linear-gradient(ghostwhite 0px 25px, darkslategray 25px 50px);
This may be the desired look, but I think that most often than not, you want it to match the element’s dimensions.
Using linear-gradient(), we can easily solve this using to top right or to bottom left values for the angle, which automatically sets the angle so the gradient goes from corner to corner.
.gradient
background: repeating-linear-gradient(to top right, ghostwhite 0px 25px, darkslategray 25px 50px);
However, we don’t have that type of syntax for other gradients, like a conic-gradient(). For example, the next conic gradient has a fixed angle and won’t change upon resizing the element.
.gradient
background: conic-gradient(from 45deg, #84a59d 180deg, #f28482 180deg);
Luckily, we can fix this using atan()! We can look at the gradient as a right-angled triangle, where the width is the adjacent side and the height the opposite side:

Then, we can get the angle using this formula:
.gradient
--angle: atan(var(--height-gradient) / var(--width-gradient));
Since conic-gradient() starts from the top edge — conic-gradient(from 0deg) — we’ll have to shift it by 90deg to make it work.
.gradient
--rotation: calc(90deg - var(--angle));
background: conic-gradient(from var(--rotation), #84a59d 180deg, #f28482 180deg);
You may be wondering: can’t we do that with a linear gradient? And the answer is, yes! But this was just an example to showcase atan(). Let’s move on to more interesting stuff that’s unique to conic gradients.
I got the next example from Ana Tudor’s post on “Variable Aspect Ratio Card With Conic Gradients”:
Pretty cool, right?. Sadly, Ana’s post is from 2021, a time when trigonometric functions were specced out but not implemented. As she mentions in her article, it wasn’t possible to create these gradients using atan(). Luckily, we live in the future! Let’s see how simple they become with trigonometry and CSS.
We’ll use two conic gradients, each of them covering half of the card’s background.

To save time, I’ll gloss over exactly how to make the original gradient, so here is a quick little step-by-step guide on how to make one of those gradients in a square-shaped element:
Since we’re working with a perfect square, we can fix the --angle and --rotation to be 45deg, but for a general use case, each of the conic-gradients would look like this in CSS:
.gradient
background:
/* one below */
conic-gradient(
from var(--rotation) at bottom left,
#b9eee1 calc(var(--angle) * 1 / 3),
#79d3be calc(var(--angle) * 1 / 3) calc(var(--angle) * 2 / 3),
#39b89a calc(var(--angle) * 2 / 3) calc(var(--angle) * 3 / 3),
transparent var(--angle)
),
/* one above */
conic-gradient(
from calc(var(--rotation) + 180deg) at top right,
#fec9d7 calc(var(--angle) * 1 / 3),
#ff91ad calc(var(--angle) * 1 / 3) calc(var(--angle) * 2 / 3),
#ff5883 calc(var(--angle) * 2 / 3) calc(var(--angle) * 3 / 3),
transparent var(--angle)
);
And we can get those --angle and --rotation variables the same way we did earlier — using atan(), of course!
.gradient
--angle: atan(var(--height-gradient) / var(--width-gradient));
--rotation: calc(90deg - var(--angle));
What about atan2()?
The last example was all abou atan(), but I told you we would also look at the atan2() function. With atan(), we get the angle when we divide the opposite side by the adjacent side and pass that value as the argument. On the flip side, atan2() takes them as separate arguments:
atan(opposite/adjacent)atan2(opposite, adjacent)
What’s the difference? To explain, let’s backtrack a bit.
We used atan() in the context of triangles, meaning that the adjacent and opposite sides were always positive. This may seem like an obvious thing since lengths are always positive, but we won’t always work with lengths.
Imagine we are in a x-y plane and pick a random point on the graph. Just by looking at its position, we can know its x and y coordinates, which can have both negative and positive coordinates. What if we wanted its angle instead? Measuring it, of course, from the positive x-axis.

Well, remember from the last article in this series that we can also define tan() as the quotient between sin() and cos():

Also recall that when we measure the angle from the positive x-axis, then sin() returns the y-coordinate and cos() returns the x-coordinate. So, the last formula becomes:

And applying atan(), we can directly get the angle!

This formula has one problem, though. It should work for any point in the x-y plane, and since both x and y can be negative, we can confuse some points. Since we are dividing the y-coordinate by the x-coordinate, in the eyes of atan(), the negative y-coordinate looks the same as the negative x-coordinate. And if both coordinates are negative, it would look the same as if both were positive.

To compensate for this, we have atan2(), and since it takes the y-coordinate and x-coordinate as separate arguments, it’s smart enough to return the angle everywhere in the plane!
Let’s see how we can put it to practical use.
Following the mouse
Using atan2(), we can make elements react to the mouse’s position. Why would we want to do that? Meet my friend Helpy, Clippy’s uglier brother from Microsoft.
Helpy wants to always be looking at the user’s mouse, and luckily, we can help him using atan2(). I won’t go into too much detail about how Helpy is made, just know that his eyes are two pseudo-elements:
.helpy::before,
.helpy::after
/* eye styling */
To help Helpy, we first need to let CSS know the mouse’s current x-y coordinates. And while I may not like using JavaScript here, it’s needed in order to pass the mouse coordinates to CSS as two custom properties that we’ll call --m-x and --m-y.
const body = document.querySelector("body");
// listen for the mouse pointer
body.addEventListener("pointermove", (event) =>
// set variables for the pointer's current coordinates
let x = event.clientX;
let y = event.clientY;
// assign those coordinates to CSS custom properties in pixel units
body.style.setProperty("--m-x", `$Math.round(x)px`);
body.style.setProperty("--m-y", `$Math.round(y)px`);
);Helpy is currently looking away from the content, so we’ll first move his eyes so they align with the positive x-axis, i.e., to the right.
.helpy::before,
.helpy::after
rotate: 135deg;
Once there, we can use atan2() to find the exact angle Helpy has to turn so he sees the user’s mouse. Since Helpy is positioned at the top-left corner of the page, and the x and y coordinates are measured from there, it’s time to plug those coordinates into our function: atan2(var(--m-y), var(--m-x)).
.helpy::before,
.helpy::after
/* rotate the eyes by it's starting position, plus the atan2 of the coordinates */
rotate: calc(135deg + atan2(var(--m-y), var(--m-x)));
We can make one last improvement. You’ll notice that if the mouse goes on the little gap behind Helpy, he is unable to look at the pointer. This happens because we are measuring the coordinates exactly from the top-left corner, and Helpy is positioned a little bit away from that.
To fix this, we can translate the origin of the coordinate system directly on Helpy by subtracting the padding and half its size:

Which looks like this in CSS:
.helpy::before,
.helpy::after
rotate: calc(
135deg +
atan2(
var(--m-y) - var(--spacing) - var(--helpy-size) / 2,
var(--m-x) - var(--spacing) - var(--helpy-size) / 2
)
);
This is a somewhat minor improvement, but moving the coordinate origin will be vital if we want to place Helpy in any other place on the screen.
Extra: Getting the viewport (and anything) in numbers
I can’t finish this series without mentioning a trick to typecast different units into simple numbers using atan2() and tan(). It isn’t directly related to trigonometry but it’s still super useful. It was first described amazingly by Jane Ori in 2023, and goes as follows.
If we want to get the viewport as an integer, then we can…
@property --100vw
syntax: "<length>;";
initial-value: 0px;
inherits: false;
:root
--100vw: 100vw;
--int-width: calc(10000 * tan(atan2(var(--100vw), 10000px)));
And now: the --int-width variable holds the viewport width as an integer. This looks like magic, so I really recommend reading Jane Ori’s post to understand it. I also have an article using it to create animations as the viewport is resized!
What about reciprocals?
I noticed that we are still lacking the reciprocals for each trigonometric function. The reciprocals are merely 1 divided by the function, so there’s a total of three of them:
- The secant, or
sec(x), is the reciprocal ofcos(x), so it’s1 / cos(x). - The cosecant, or
csc(x), is the reciprocal ofsin(x), so it’s1 / sin(x). - The cotangent, or
cot(x)is the reciprocal oftan(x), so it’s1 / tan(x).
The beauty of sin(), cos() and tan() and their reciprocals is that they all live in the unit circle we’ve looked at in other articles in this series. I decided to put everything together in the following demo that shows all of the trigonometric functions covered on the same unit circle:
That’s it!
Welp, that’s it! I hope you learned and had fun with this series just as much as I enjoyed writing it. And thanks so much for those of you who have shared your own demos. I’ll be rounding them up in my Bluesky page.
CSS Trigonometric Functions: The “Most Hated” CSS Feature
sin()andcos()tan()asin(),acos(),atan()andatan2()(You are here!)
The “Most Hated” CSS Feature: asin(), acos(), atan() and atan2() originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.