No feature is truly “the worst” in CSS, right? After all, it’s all based on opinion and personal experience, but if we had to reach a consensus, checking the State of CSS 2025 results would be a good starting point. I did exactly that, jumped into the awards section, and there I found it: the “Most Hated Feature,” a title no CSS should have bear…
This shocks me, if I’m being honest. Are really trigonometric functions really that hated? I know “hated” is not the same as saying something is “worst”, but it still has an awful ring to it. And I know I’m being a little dramatic here, since only “9.1% of respondents truly hate trigonometry.” But that’s still too much shade being thrown for my taste.
I want to eliminate that 9.1%. So, in this series, I want to look at practical uses for CSS trigonometric functions. We’ll tackle them in pieces because there’s a lot to take in and I find it easiest to learn and retain information when it’s chunked into focused, digestible pieces. And we’ll start with what may be the most popular functions of the “worst” feature: sin() and cos() .
CSS Trigonometric Functions: The “Most Hated” CSS Feature sin() and cos() (You are here!) Tackling the CSS tan() Function (coming soon) Inverse functions: asin() , acos() , atan() and atan2() (coming soon)
What the heck are cos() and sin() anyway?
This section is for those who cos() and sin() don’t quite click yet, or simply want a refresher. If you aced trigonometry quizzes in high school, feel free to skip ahead to the next section!
What I find funny about cos() and sin() — and also why I think there is confusion around them — is the many ways we can describe them. We don’t have to look too hard. A quick glance at this Wikipedia page has an eye-watering number of super nuanced definitions.
This is a learning problem in the web development field. I feel like some of those definitions are far too general and lack detail about the essence of what trigonometric functions like sin() and cos() can do. Conversely, other definitions are overly complex and academic, making them tough to grok without an advanced degree.
Let’s stick to the sweet middle spot: the unit circle.
Meet the unit circle. It is a circle with a radius of one unit:
Right now it’s alone… in space. Let’s place it on the Cartesian coordinate system (the classic chart with X and Y axes). We describe each point in space in Cartesian coordinates:
The X coordinate: The horizontal axis, plotting the point towards the left or right. The Y coordinate: The vertical axis, plotting the point towards the top or bottom.
We can move through the unit circle by an angle, which is measured from the positive X-axis going counter-clockwise.
CodePen Embed Fallback
We can go in a clockwise direction by using negative angles. As my physics teacher used to say, “Time is negative!”
Notice how each angle lands on a unique point in the unit circle. How else can we describe that point using Cartesian coordinates?
When the angle is 0° the X and Y coordinates are 1 and 0 ( 1 , 0 ), respectively. We can deduce the Cartesian coordinates for other angles just as easily, like 90° , 180° and 270° . But for any other angle, we don’t know where the point is initially located on the unit circle.
If only there were a pair of functions that take an angle and give us our desired coordinates…
You guessed it, the CSS cos() and sin() functions do exactly that. And they’re very closely related, where cos() is designed to handle the X coordinate and sin() returns the Y coordinate.
Play with the toggle slider in the following demo to see the relationship between the two functions, and notice how they form a right triangle with the initial point on the unit circle:
CodePen Embed Fallback
I think that’s all you really need to know about cos() and sin() for the moment. They’re mapped to Cartesian coordinates, which allows us to track a point along the unit circle with an angle, no matter what size that circle happens to be.
Let’s dive into what we can actually use cos() and sin() for our everyday CSS work. It’s always good to put a little real-world context to theoretical concepts like math.
Circular layouts
If we go by the unit circle definition of cos() and sin() , then it’s easy to see how they might be used to create circular layouts in CSS. The initial setup is a single row of circular elements:
CodePen Embed Fallback
Say we want to place each circular item around the outline of a larger circle instead. First, we would let CSS know the total number of elements and also each element’s index (the order it’s in), something we can do with an inline CSS variable that holds each order in the position:
elements in the outer unordered list that contain the other lists using display: contents .
.waves > li { display: contents; }
Notice how one of the chains is the “principal” while the other is the “secondary.” The difference is that the “secondary” chain is positioned behind the “principal” chain. I’m using slightly different background colors for the items in each chain, so it’s easier to distinguish one from the other as you scroll through the block-level overflow.
CodePen Embed Fallback
We can reorder the chains using a stacking context:
.principal { position: relative; z-index: 2; } .secondary { position: absolute; }
This positions one chain on top of the other. Next, we will adjust each item’s vertical position with the “hated” sin() and cos() functions. Remember, they’re sorta like reflections of one another, so the variance between the two is what offsets the waves to form two intersecting chains of items:
.principal { /* ... */ li { transform: translateY(calc(sin(60deg * var(--i)) * var(--shape-size) / 2)); } } .secondary { /* ... */ li { transform: translateY(calc(cos(60deg * var(--i)) * var(--shape-size) / 2)); } }
We can accentuate the offset even more by shifting the .secondary wave another 60deg :
.secondary { /* ... */ li { transform: translateY(calc(cos(60deg * var(--i) + 60deg) * var(--shape-size) / 2)); } }
The next demo shows how the waves intersect at an offset angle of 60deg . Adjust the slider toggle to see how the waves intersect at different angles:
CodePen Embed Fallback
Oh, I told you this could be used in a practical, real-world way. How about adding a little whimsy and flair to a hero banner:
CodePen Embed Fallback
Damped oscillatory animations
The last example got me thinking: is there a way to use sin() and cos() ‘s back and forth movement for animations? The first example that came to mind was an animation that also went back and forth, something like a pendulum or a bouncing ball.
This is, of course, trivial since we can do it in a single animation declaration:
.element { animation: someAnimation 1s infinite alternate; }
This “back and forth” animation is called oscillatory movement. And while cos() or sin() are used to model oscillations in CSS, it would be like reinventing the wheel (albeit a clunkier one).
I’ve learned that perfect oscillatory movement — like a pendulum that swings back and forth in perpetuity, or a ball that never stops bouncing — doesn’t really exist. Movement tends to decay over time, like a bouncing spring:
⚠️ Auto-playing media
There’s a specific term that describes this: damped oscillatory movement. And guess what? We can model it in CSS with the cos() function! If we graph it over time, then we will see it goes back and forth while getting closer to the resting position1.
Wikipedia has another animated example that nicely demonstrates what damped oscillation looks like.
In general, we can describe damped oscillation over time as a mathematical function:
It’s composed of three parts:
e −γt : Due to the negative exponent, it becomes exponentially smaller as time passes, bringing the movement to a gradual stop. It is multiplied by a damping constant (γ) that specifies how quickly the movement should decay.
Due to the negative exponent, it becomes exponentially smaller as time passes, bringing the movement to a gradual stop. It is multiplied by a damping constant (γ) that specifies how quickly the movement should decay. a: This is the initial amplitude of the oscillation, i.e., the element’s initial position.
This is the initial amplitude of the oscillation, i.e., the element’s initial position. cos(ωt−α): This gives the movement its oscillation as time passes. Time is multiplied by frequency (ω), which determines an element’s oscillation speed2. We can also subtract from time α, which we can use to offset the initial oscillation of the system.
Okay, enough with all the theory! How do we do it in CSS? We’ll set the stage with a single circle sitting all by itself.
CodePen Embed Fallback
We have a few CSS variables we can define that will come in handy since we already know the formula we’re working with:
:root { --circle-size: 60px; --amplitude: 200px; /* The amplitude is the distance, so let's write it in pixels*/ --damping: 0.3; --frequency: 0.8; --offset: calc(pi/2); /* This is the same as 90deg! (But in radians) */ }
Given these variables, we can peek at what the animation would look like on a graph using a tool like GeoGebra:
From the graph, we can see that the animation starts at 0px (thanks to our offset), then peaks around 140px and dies out around 25s in. I, for one, won’t be waiting 25 seconds for the animation to end, so let’s create a --progress property that will animate between 0 to 25 , and will act as our “time” in the function.
Remember that to animate or transition a custom property, we’ve gotta register it with the @property at-rule.
@property --progress { syntax: ""; initial-value: 0; inherits: true; } @keyframes movement { from { --progress: 0; } to { --progress: 25; } }
What’s left is to implement the prior formula for the element’s movement, which, written in CSS terms, looks like this:
.circle { --oscillation: calc( (exp(-1 * var(--damping) * var(--progress))) * var(--amplitude) * cos(var(--frequency) * (var(--progress)) - var(--offset)) ); transform: translateX(var(--oscillation)); animation: movement 1s linear infinite; }
CodePen Embed Fallback
This gives a pretty satisfying animation by itself, but the damped motion is only on the x-axis. What would it look like if, instead, we applied the damped motion on both axes? To do this, we can copy the same oscillation formula for x, but replace the cos() with sin() .
.circle { --oscillation-x: calc( (exp(-1 * var(--damping) * var(--progress))) * var(--amplitude) * cos(var(--frequency) * (var(--progress)) - var(--offset)) ); --oscillation-y: calc( (exp(-1 * var(--damping) * var(--progress))) * var(--amplitude) * sin(var(--frequency) * (var(--progress)) - var(--offset)) ); transform: translateX(var(--oscillation-x)) translateY(var(--oscillation-y)); animation: movement 1s linear infinite; }
CodePen Embed Fallback
This is even more satisfying! A circular and damped motion, all thanks to cos() and sin() . Besides looking great, how could this be used in a real layout?
We don’t have to look too hard. Take, for example, this sidebar I recently made where the menu items pop in the viewport with a damped motion:
CodePen Embed Fallback
Pretty neat, right?!
More trigonometry to come!
Well, finding uses for the “most hated CSS feature” wasn’t that hard; maybe we should start showing some love to trigonometric functions. But wait. There are still several trigonometric functions in CSS we haven’t talked about. In the following posts, we’ll keep exploring what trig functions (like tan() and inverse functions) can do in CSS.
CSS Trigonometric Functions: The “Most Hated” CSS Feature sin() and cos() (You are here!) Tackling the CSS tan() Function (coming soon) Inverse functions: asin() , acos() , atan() and atan2() (coming soon)
Also, before I forget, here is another demo I made using cos() and sin() that didn’t make the cut in this article, but it is still worth checking out because it dials up the swirly-ness from the last example to show how wacky we can get.
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8