The new progress() function in CSS
Imagine a responsive hero image that becomes more transparent as the viewport gets narrower, helping text readability on small screens or a card that scales up slightly as the viewport grows, adding a subtle polish.
Until now, achieving these effects required complex calc()
expressions or JavaScript, often leading to brittle solutions. But with the new CSS progress()
function, you can express these relationships cleanly and intuitively.
With the new CSS progress()
function, you can express “how far” the viewport width has moved between a minimum and maximum size, and map that directly to opacity.
The progress function returns how far a value is between two bounds.
Here’s what the function definition looks like:
progress(<value>, <start>, <end>)
Here, <value>
is the current value you want to evaluate, <start>
is the minimum bound, and <end>
is the maximum bound. The function returns a number between 0
and 1
, where 0
means the value is at or below the start, and 1
means it’s at or above the end. Values in between are represented as a decimal between 0
and 1
.
Where can you use it?
You can use progress()
wherever you need a normalized 0–1 value from mixed units. You can treat progress() as a unit-agnostic “interpolator” for responsive and state-driven effects. Common scenarios include:
-
Visual polish tied to viewport size: fade images, adjust blur, tweak letter-spacing, or scale components as 100vw moves between small and large breakpoints, without media queries.
-
Adaptive layout thresholds: smoothly ramp padding, gap, border-radius, or container width between min/max sizes so UI densifies on small screens and breathes on large ones.
-
Dynamic spacing and alignment: adjust margins, padding, or alignment based on viewport size to create a more fluid and responsive layout.
-
Scroll and view-based choreography: combine with scroll-driven animations or animation-timeline to map element opacity, transform, or filter intensity to how far a section has scrolled into view.
-
Component states and data-driven styling: map numeric CSS variables (e.g., a rating 0–100, audio volume 0–1, or battery level in px) to opacity, color stops, or thickness, even when inputs use different units.
-
Fluid typography and readability: blend font-size, word/letter-spacing, and line-height as the viewport changes for better legibility; tune text-shadow or background-overlay strength to maintain contrast.
-
Theme transitions: interpolate between two colors using color-mix or relative color syntax, using progress() as the mixing ratio for smooth dark/light adjustments across sizes.
-
Micro-interactions without JS: subtle scale/tilt on cards, emphasis rings via outline-width, or highlight intensity via filter: brightness, all tied to a single progress() range.
-
Performance-friendly fallbacks: when calc() can’t mix units, progress() bridges px, rem, vw so you can clamp the result and avoid complex JS or custom properties gymnastics.
A practical example
Here’s a practical snippet that fades the image from fully visible on large screens to slightly transparent on small screens.
.hero img {
/* As viewport goes from 1200px down to 480px, opacity eases from 1 to 0.4 */
opacity: clamp(0.4, 1 - progress(100vw, 480px, 1200px) * 0.6, 1);
}
In this example:
- At widths ≤ 480px,
progress(100vw, 480px, 1200px)
is 0, so opacity is 1 − 0 × 0.6 = 1. - At widths ≥ 1200px, progress is 1, so opacity is 1 − 1 × 0.6 = 0.4.
- Between those, it smoothly interpolates, even though
100vw
is in viewport units and the bounds are in pixels — a key advantage overcalc()
.
Another real-world use: scale a card slightly as the viewport grows, without media queries:
.card {
transform: scale(calc(1 + progress(100vw, 360px, 1024px) * 0.05));
}
Here, the card scales from 1.00 at 360px to about 1.05 at 1024px, giving a subtle, device-size-aware polish.
I put together the above examples in this CodePen so you can see them in action.
See the Pen The progress() function example by Amit Merchant (@amit_merchant) on CodePen.
Browser support
As of today, the CSS progress()
function is available in modern Chromium browsers and Safari 26. Firefox does not currently list support for progress()
in its CSS value functions yet.
So, you can fallback to using clamp()
or calc()
in scenarios where you need to support Firefox, for now (e.g., clamp(calc((value-start)/(end-start)), 0, 1)
where units permit).
👋 Hi there! This is Amit, again. I write articles about all things web development. If you enjoy my work (the articles, the open-source projects, my general demeanour... anything really), consider leaving a tip & supporting the site. Your support is incredibly appreciated!