Articles

CSS Trigonometric Functions: sin(), cos(), atan2() Explained

CSS trig functions like sin(), cos(), and atan2() let you compute angles and coordinates directly in stylesheets, no JavaScript required. How they work.

Takina Takina · · 4 min read
Close-up of CSS code on a screen

CSS trigonometric functionssin(), cos(), tan(), asin(), acos(), atan(), and atan2() — let a stylesheet compute values from angles the way a graphics library would, so layouts that used to require JavaScript to place elements on a circle or compute a rotation can now be expressed as plain CSS math.

They live alongside the other CSS math functions — calc(), min(), max(), clamp() — and can be nested inside them freely. A trig function returns a plain number (or, for atan2(), an angle), which you then combine with a length unit to get something a property can consume.

The available functions

  • sin(angle), cos(angle) — return a unitless number between -1 and 1, given an angle in deg, rad, grad, or turn.
  • tan(angle) — returns a unitless number (unbounded, since tangent has asymptotes).
  • asin(number), acos(number), atan(number) — the inverses; take a unitless number and return an angle.
  • atan2(y, x) — takes two lengths (or numbers) and returns the angle of the point (x, y) from the origin, handling all four quadrants correctly, unlike atan() alone.

Because sin() and cos() return unitless numbers, you multiply them by a length to get a usable value:

.dot {
  --angle: 45deg;
  --radius: 120px;
  transform: translate(
    calc(cos(var(--angle)) * var(--radius)),
    calc(sin(var(--angle)) * var(--radius))
  );
}

What they’re for: circular layouts without JavaScript

The clearest use case is placing items evenly around a circle — a common pattern for icon menus, radial navigation, or chart labels. Before trig functions, this meant computing x/y offsets in JavaScript and writing them as inline styles or CSS custom properties per element. Now the browser does the math:

.item {
  --i: 0; /* set per element, e.g. via nth-child or a custom property */
  --total: 8;
  --angle: calc(360deg / var(--total) * var(--i));
  --radius: 140px;
  position: absolute;
  transform: translate(
    calc(cos(var(--angle)) * var(--radius)),
    calc(sin(var(--angle)) * var(--radius))
  );
}

Each --i value (set with style="--i: 2" or generated via a preprocessor loop) rotates the point around the circle. This composes well with CSS container queries — the radius can itself respond to the container’s size, keeping the layout proportional without a resize listener.

atan2() for pointing at things

atan2(y, x) is the function you reach for when you need an angle from a pair of coordinates, rather than a coordinate from an angle. A common use is orienting an arrow or needle toward a target — a compass widget, a “distance from center” indicator, or a custom slider thumb that should visually point along its track.

.needle {
  --dx: 60px;
  --dy: -30px;
  rotate: atan2(var(--dy), var(--dx));
}

Unlike atan(), which only covers a 180-degree range and loses the sign information needed to tell “up-left” from “down-right,” atan2() takes both components and resolves the full 360-degree circle correctly — the same reason graphics APIs expose atan2 instead of relying on atan alone.

Combining trig with clamp() and custom properties

Trig functions are most useful nested inside calc(), and by extension inside clamp() for responsive variants. A wave-like decorative border, a fan of rotated cards, or a dial gauge that sweeps between a min and max angle based on a custom property are all now single CSS rules instead of a <canvas> element or a resize-driven script. Pairing trig math with fluid typography via clamp() is a natural fit: both techniques replace a JavaScript calculation with a formula the browser evaluates on every layout pass, for free, in sync with paint.

Angle-based custom properties also compose with CSS nesting cleanly, since you can define a --angle once on a parent and reference it in the trig math of every descendant selector without repeating the calculation.

Performance: computed at layout time, not per frame

A common worry is that trig math is “expensive” to run in a stylesheet. In practice, these values are resolved during layout and paint like any other computed style — the browser isn’t running a JavaScript loop, it’s evaluating a formula as part of the same computation it already performs for calc(). For a static circular layout, the cost is paid once, not per animation frame. If you’re animating the underlying custom property (rotating a dial, say), the cost is comparable to animating any other transform, and the usual advice about CSS transitions vs animations — prefer transform and opacity, avoid layout-triggering properties — still applies.

A worked example: a five-point rating dial

.rating {
  --value: 4; /* out of 5 */
  --angle: calc(180deg * (var(--value) / 5) - 90deg);
}

.rating .needle {
  rotate: var(--angle);
  transform-origin: bottom center;
}

No JavaScript computed that rotation — it falls straight out of a linear interpolation and a unit conversion, both of which CSS can now do natively.

The takeaway

CSS trig functions bring sin(), cos(), tan(), and their inverses — plus the quadrant-aware atan2() — into stylesheets, so circular layouts, pointing indicators, and angle-driven dials no longer need a JavaScript computation to produce values that used to only exist in a <canvas> or SVG context. They nest inside calc() and clamp() like any other CSS math function, resolve at layout time rather than per animation frame, and pair naturally with custom properties to keep angle-based designs declarative and responsive.

Takina Takina · · 4 min read

CSS field-sizing Property Explained

The CSS field-sizing property lets form controls like textareas grow to fit their content automatically, without JavaScript resize listeners.

#CSS #Web Development #Frontend