CSS Math Functions round(), mod() and rem() in Practice
AI generated
{ }
@
CSS · Math Functions · Layout
round(), mod() and rem()
Native CSS math for grid sizes and cyclical values

With round(), mod() and rem(), the browser computes values directly in the stylesheet declaration, without a script pre-calculating numbers and injecting them as a custom property. Anyone who needs to snap sizes to a grid or repeat values cyclically, for example carousel indices or stripe patterns, gets a tool that can be embedded directly inside calc().

12 min read round() · mod() · rem() CSS Values and Units Level 4

1. Why CSS now needs its own rounding and modulo functions

Until recently, CSS had no way to round a computed value to a specific step size or express a cyclical repetition without falling back on JavaScript or a fixed set of hand-written selectors. A layout that needed to snap elements to an 8-pixel grid either had to pick every value by hand or let a script compute values on page load and inject them as a style attribute. That works, but breaks every time viewport width or another input value changes in a way CSS itself could react to.

With round(), mod() and rem() from CSS Values and Units Level 4, the language gets exactly this capability built in. All three are pure math functions that, like calc(), can appear anywhere a numeric value is expected, and they combine freely with calc(), custom properties and even container query units. That moves logic that used to require JavaScript back into the declaration, where it is easier to maintain and faster to render.

2. round(): snapping values to a step size

The function round(<strategy>, <value>, <interval>) rounds a value to a multiple of the given interval. The strategy determines the rounding direction: nearest rounds to the closest multiple, up always rounds upward, down always downward, and to-zero rounds toward zero. Omitting the strategy behaves like round(nearest, ...).

This becomes practical the moment a layout needs to align to a fixed base grid, for example an 8-pixel spacing system. Instead of manually snapping every width to a multiple of 8, round() handles it for computed, dynamic values, for example when an element's width depends on viewport width but still needs to land on the grid.


.grid-item {
  /* Element width depends on the viewport but always lands
     on a multiple of 8px */
  width: round(nearest, 100vw / 3, 8px);
}

.card {
  /* Padding never gets smaller than 8px, always rounded up
     to 8px steps -- avoids odd in-between values */
  padding: round(up, 1.2rem, 8px);
}

3. mod(): cyclical values for patterns and indices

mod(A, B) computes the remainder of dividing A by B, taking the sign of B. The result therefore always falls in the same sign range as the divisor, regardless of whether A is positive or negative. This matches the classic mathematical modulo operation known from Python, making it a perfect fit for cyclical patterns where negative intermediate values still need to wrap sensibly.

A typical use case is a carousel or gallery where an active index is set through a custom property and CSS needs to derive a cyclical state from it, for example highlighting every third tile regardless of how high the overall index counts. Stripe patterns using nth-child-like logic over computed values also benefit from mod() to express the wraparound without extra classes.


.carousel {
  --active-index: 7;
  /* Reduces an arbitrarily high index to the 0..2 range,
     e.g. to always pick one of three state colors */
  --cycle: mod(var(--active-index), 3);
}

.stripe {
  /* Gradient offset wraps cyclically, negative values still
     stay within the 0..359 range */
  --hue-offset: mod(var(--index) * 47, 360deg);
  background: hsl(var(--hue-offset) 70% 50%);
}

4. rem() vs. mod(): the difference with negative numbers

At first glance, mod() and rem(A, B) look identical, since both compute a division remainder. The crucial difference only shows up with negative input values: rem() takes the sign of A, the dividend, while mod() takes the sign of B, the divisor. With purely positive values both functions return exactly the same result, which is why the distinction gets glossed over in many tutorials.

For a layout working with negative offsets, for example a position that can shift left or right relative to an anchor point, rem() often gives the more intuitive result, because a negative input produces a negative result instead of jumping into the positive range. For cyclical states like carousel indices, on the other hand, mod() is almost always the right choice, since a clean, always-positive wraparound is what's wanted.

5. Combining with calc(): nested expressions

All three functions are full-fledged mathematical CSS expressions and can be nested inside calc() or used as an argument to another math function. This allows complex layout rules in a single declaration, for example a width that first derives a cyclical offset through mod() and then combines it with a fixed base size via calc().

It matters that the arguments stay type-compatible: mixing pixels with degrees without a shared unit or a deliberate conversion produces an invalid value that the browser silently ignores. It therefore pays off to consistently declare custom properties for intermediate values with an explicit unit, rather than passing unitless numbers through deeply nested calc() expressions.


.pattern-tile {
  --i: 5;
  /* mod() produces a cyclical offset 0..2, calc() adds
     that to a fixed base width -- all in one declaration */
  width: calc(120px + mod(var(--i), 3) * 20px);
}

.snapped {
  /* nested: round first, then reuse the result inside
     another calc() */
  margin-top: calc(round(nearest, var(--raw-offset), 4px) + 2px);
}

6. Browser support and feature detection

round(), mod() and rem() have shipped in current versions of Chrome, Edge, Firefox and Safari since 2023/2024 and now count as production ready for projects that don't need to support very old browser versions. For projects with stricter support requirements, checking current numbers before adoption is worthwhile, since the minimum supported version differs per browser.

A reliable safeguard is @supports: inside an @supports (width: round(nearest, 1px, 1px)) rule, you can test whether the browser understands the function, and a fallback value outside the rule automatically applies for older browsers. That keeps the layout functional even without native rounding, just without the exact grid alignment.


.grid-item {
  /* Fallback for browsers without round() support */
  width: 33.33vw;
}

@supports (width: round(nearest, 1px, 1px)) {
  .grid-item {
    width: round(nearest, 100vw / 3, 8px);
  }
}

7. Practical example: a CSS grid with guaranteed integer track widths

A common problem in responsive grids is sub-pixel widths that can cause visible misalignment between grid tracks, especially with image galleries that have visible borders. With round(), each track width can be explicitly forced to a whole pixel number instead of relying on the browser's sub-pixel rounding, which can vary slightly between rendering engines.

In practice this is often combined with clamp() to additionally set a minimum and maximum width, while round() handles grid alignment within those bounds. The result is a grid that looks clean on every viewport without JavaScript recalculating anything on resize.

8. Practical example: carousel states with mod() and no JavaScript counter

An image carousel with several visible tiles often needs to know a tile's position relative to the currently active tile, to scale or dim it accordingly. With mod(), this relative state can be computed directly from a running number and the current index position, without JavaScript having to tag every tile with a separate class.

That drastically reduces the amount of DOM manipulation: JavaScript only sets a single custom property for the active index, and CSS uses mod() to independently compute for every tile whether it is active, adjacent, or far away. That keeps interaction logic lean and style computation where it executes fastest.


.carousel-item {
  --distance: mod(var(--item-index) - var(--active-index) + var(--total), var(--total));
}

.carousel-item[style*="--distance: 0"] {
  transform: scale(1.1);
  opacity: 1;
}

9. Limits and common pitfalls

A common trap is assuming round(), mod() and rem() can express arbitrarily complex logic at runtime like JavaScript functions do. In reality, these are pure, declarative math functions without conditionals, loops or state management, which is why more complex state machines still rely on custom properties and targeted selectors. For simple rounding and cycle logic, though, they are entirely sufficient and replace exactly the part that used to require JavaScript.

A second pitfall is division by zero: mod(A, 0) and rem(A, 0) produce an invalid value that the browser treats like a malformed declaration and ignores. Anyone pulling the divisor from a custom property that could theoretically be set to zero should provide a fallback through var(--divisor, 1) to guard against that scenario.

Function Computation Sign of the result Typical use
round(nearest, A, B) Rounds A to a multiple of B same as A Grid sizes, spacing systems
round(up, A, B) Always rounds A upward to B same as A Guaranteeing minimum spacing
mod(A, B) Remainder of A / B same as B, the divisor Cyclical patterns, indices
rem(A, B) Remainder of A / B same as A, the dividend Relative offsets with negative values

Mironsoft

Modern CSS, layout architecture and rendering performance

CSS that stays maintainable instead of breaking with every change?

We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.

CSS Audit

Systematically uncovering specificity issues, cascade conflicts and unused selectors.

Architecture Refactoring

Introducing cascade layers, custom properties and design tokens cleanly.

Performance Tuning

Fixing layout thrashing, expensive selectors and rendering bottlenecks.

10. Summary

round(), mod() and rem() in CSS: The Essentials at a Glance

round()

Rounds a value to a multiple of an interval, using the nearest, up, down or to-zero strategies.

mod()

Division remainder that takes the sign of the divisor, ideal for cyclical states like carousel indices.

rem()

Division remainder that takes the sign of the dividend, better suited to negative relative offsets.

Combination

All three functions can be nested inside calc() freely, as long as argument units stay compatible.

11. FAQ: round(), mod() and rem() in CSS: The Essentials at a Glance

1What does round() do in CSS?
round() rounds a computed value to a multiple of a given interval, with a selectable strategy: nearest, up, down or to-zero.
2How do mod() and rem() differ?
Both compute a division remainder, but mod() takes the sign of the divisor while rem() takes the sign of the dividend. For positive numbers, the results are identical.
3When should I use mod() instead of rem()?
For cyclical states like carousel indices or gradient offsets, where the result should always stay positive, mod() is the right choice.
4Can I nest round(), mod() and rem() inside calc()?
Yes, all three are full mathematical expressions and can be nested inside calc() or within each other, as long as the argument units are compatible.
5What happens on division by zero?
mod(A, 0) and rem(A, 0) produce an invalid value that the browser ignores like a malformed declaration. A fallback through var(--divisor, 1) prevents this.
6Do all modern browsers support these functions?
Chrome, Edge, Firefox and Safari have supported round(), mod() and rem() in current versions since 2023/2024. For older browsers, a safeguard via @supports is recommended.
7How do I check browser support without JavaScript?
With an @supports rule that uses the function itself as the test condition, for example @supports (width: round(nearest, 1px, 1px)). A simple fallback value sits outside the rule.
8Do these functions replace JavaScript entirely?
No, only the part that is pure rounding or modulo computation. More complex state logic with conditionals or loops remains JavaScript's job.
9Can I use negative values in round()?
Yes, round() works with negative input values too and rounds in the appropriate direction based on the chosen strategy.
10Is round() suitable for grid sizes in design systems?
Yes, that is one of the main use cases: dynamic, computed widths or spacing values can be reliably snapped to a fixed spacing grid.