<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Petros Papatheodorou</title>
    <description>The latest articles on DEV Community by Petros Papatheodorou (@petros_papa21).</description>
    <link>https://dev.to/petros_papa21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4105187%2F8ea00242-a1c9-4a46-953f-7db0dc8e4dd2.jpg</url>
      <title>DEV Community: Petros Papatheodorou</title>
      <link>https://dev.to/petros_papa21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petros_papa21"/>
    <language>en</language>
    <item>
      <title>I accidentally reinvented CSS progress() and then I published it anyway.</title>
      <dc:creator>Petros Papatheodorou</dc:creator>
      <pubDate>Wed, 02 Sep 2026 00:41:19 +0000</pubDate>
      <link>https://dev.to/petros_papa21/i-accidentally-reinvented-css-progress-and-then-i-published-it-anyway-25dj</link>
      <guid>https://dev.to/petros_papa21/i-accidentally-reinvented-css-progress-and-then-i-published-it-anyway-25dj</guid>
      <description>&lt;p&gt;It all started a few days ago, when I was taking a look at &lt;a href="https://www.zeuscss.com/" rel="noopener noreferrer"&gt;Zeus&lt;/a&gt;, a CSS framework my friend Stavros Lazaris had just released. One idea in there sent me down a rabbit hole that ended with an npm package and a bit of embarrassment.  &lt;/p&gt;

&lt;p&gt;If you stay for the storytime, you will get some very usefull &lt;code&gt;css&lt;/code&gt; tips as well.&lt;br&gt;
Here is the whole chronicle.&lt;/p&gt;
&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Zeus has a fluid engine. Every spacing and type token is a &lt;code&gt;clamp()&lt;/code&gt; that scales between a min and a max as the container grows. The part that got me is the unit: it keys on &lt;code&gt;cqi&lt;/code&gt;, the container query inline unit, with &lt;code&gt;container-type: inline-size&lt;/code&gt; on &lt;code&gt;body&lt;/code&gt;. Tokens follow the container they live in. Put the same card in a sidebar and in a main column and each one sizes itself.&lt;/p&gt;

&lt;p&gt;The formula behind it is the one everybody uses, from Utopia to every fluid typography tutorial out there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;font-size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;clamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;667&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;667&lt;/span&gt;&lt;span class="nt"&gt;cqi&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it as: 16px at a 320px container, 32px at 1280px, a straight line between, pinned at both ends. The two odd numbers are an intercept and a slope, worked out once by a preprocessor and pasted in.&lt;/p&gt;

&lt;p&gt;I have seen this elegant trick before, for &lt;code&gt;font-size&lt;/code&gt;, &lt;code&gt;padding&lt;/code&gt;, &lt;code&gt;gap&lt;/code&gt;, &lt;code&gt;border-radius&lt;/code&gt;. I had never once thought about where it stops working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stops working
&lt;/h2&gt;

&lt;p&gt;Take a side drawer whose width and slide distance are fluid, 18rem on a phone up to 31rem on a desktop. Give it one fixed duration and the wide version moves 1.7 times faster than the narrow one, which feels wrong every time you open it. The duration should follow the distance. Same formula, different unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;transition-duration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;clamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;14&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;-0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;18&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;cqi&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a type error. &lt;code&gt;cqi&lt;/code&gt; is a length. You cannot add a length to a time, so the browser drops the whole declaration and the drawer snaps open with no transition at all.&lt;/p&gt;

&lt;p&gt;The formula fails the same way for every value that has no length in it. &lt;code&gt;font-weight&lt;/code&gt; as a bare number. &lt;code&gt;opacity&lt;/code&gt;. &lt;code&gt;scale&lt;/code&gt;. Anything in degrees. The slope carries &lt;code&gt;cqi&lt;/code&gt;, so the result is a length, and a length is only ever welcome in a length property.&lt;/p&gt;

&lt;p&gt;What people actually do is pick one value and live with it. One duration that is tolerable at both ends, one weight, one angle. If it bothers you enough you split it once with a media query, phone and desktop. Anything smoother than that means JavaScript and a ResizeObserver.&lt;/p&gt;

&lt;p&gt;(And yes, I also wanted a button that leans seven degrees on a narrow card and stands up straight on a wide one. No reason, I was just playing and that's how i hit the wall.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Swap the order of operations
&lt;/h2&gt;

&lt;p&gt;The fix is a reordering. Divide first, multiply second.&lt;/p&gt;

&lt;p&gt;Dividing one length by another gives a plain number. &lt;code&gt;calc()&lt;/code&gt; has accepted that since 2023 (CSS Values 4: Chrome 119, Safari 17, Firefox 116). So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;clamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="nt"&gt;cqi&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;320&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;960&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--t&lt;/code&gt; is 0 at a 320px container, 1 at 1280px, a fraction between, and it has no unit. It is progress along the range, and the browser recomputes it on every layout.&lt;/p&gt;

&lt;p&gt;Now any property that takes &lt;code&gt;calc()&lt;/code&gt; can use it, in its own unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;rotate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-7deg&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;14&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;transition-duration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;14&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;96&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;font-weight&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;200&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;700&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;letter-spacing&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;02&lt;/span&gt;&lt;span class="nt"&gt;em&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;37&lt;/span&gt;&lt;span class="nt"&gt;em&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;clip-path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="nt"&gt;polygon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;24&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;24&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;--t&lt;/code&gt; for all of them. Declare it on a container and every descendant reads it. Declare it again on an inner container with its own range and everything inside follows the inner width. The &lt;code&gt;cqi&lt;/code&gt; inside the variable resolves against whichever container the value is used in, which is exactly what you want and slightly surprising the first time.&lt;/p&gt;

&lt;p&gt;A bonus I had not planned is that descending ranges work! &lt;code&gt;calc(26rem - 24rem * var(--t))&lt;/code&gt; shrinks. The classic form cannot do that, because &lt;code&gt;clamp(26rem, ..., 2rem)&lt;/code&gt; returns 26rem forever when min is bigger than max. My own first helper had that bug, and I only caught it because I measured the computed width and got 26rem at every size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it in Sass
&lt;/h2&gt;

&lt;p&gt;I got tired of doing subtraction in my head, so I wrote a few functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@use&lt;/span&gt; &lt;span class="s2"&gt;"cq-lerp"&lt;/span&gt; &lt;span class="nt"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;cq-track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;240&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;560&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nf"&gt;fluid-on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.6rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="mi"&gt;.4rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fluid-on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.4rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="na"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="nf"&gt;fluid-on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-7deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;cq-te&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;fluid-on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;620&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;cq-t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cq-track&lt;/code&gt; declares two variables: &lt;code&gt;--cq-t&lt;/code&gt; (linear) and &lt;code&gt;--cq-te&lt;/code&gt; (run through a smoothstep, for things that should settle in gently). &lt;code&gt;fluid-on&lt;/code&gt; takes a min and a max in whatever unit, works out the difference and the sign, and writes the &lt;code&gt;calc()&lt;/code&gt;. &lt;code&gt;fluid()&lt;/code&gt; is the standalone version for when you want one value with its own range baked in.&lt;/p&gt;

&lt;p&gt;Then I built six buttons to push on it, each one leaning on a different unit family. Lengths that squash, degrees that tilt and skew, a bare font-weight that fattens from 200 to 900, percentages that round corners and turn up saturation, seconds that stretch a hover transition, a &lt;code&gt;clip-path&lt;/code&gt; that morphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F74x3wtklgafcpw06ymlz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F74x3wtklgafcpw06ymlz.gif" alt="Six buttons, six unit families" width="720" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then a whole app shell, twice. One stylesheet uses this. The other is what we all shipped until this year: &lt;code&gt;clamp()&lt;/code&gt; with &lt;code&gt;vw&lt;/code&gt; for every length, one fixed transition duration, and media queries stepping the font weight, which is about as far as anyone goes without JavaScript. A switch flips between them in place.&lt;/p&gt;

&lt;p&gt;Live, resizable, no JavaScript: &lt;strong&gt;&lt;a href="https://subamanis.github.io/cq-lerp/" rel="noopener noreferrer"&gt;subamanis.github.io/cq-lerp&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was pretty pleased with myself at this point. One function, every unit, zero width breakpoints, zero keyframes. It felt like the scaling half of responsive design in one move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I read the spec
&lt;/h2&gt;

&lt;p&gt;Before publishing I wanted to check that nobody had done this before. Somebody had. &lt;strong&gt;The CSS Working Group&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--t&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="nt"&gt;cqi&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;320&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1280&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS Values 5. It returns where the first argument sits between the other two, as a number from 0 to 1, clamped. It is my &lt;code&gt;--t&lt;/code&gt; line with the subtraction and division moved into the browser. Chrome 138, Safari 26, Firefox 155.&lt;/p&gt;

&lt;p&gt;I had reinvented &lt;code&gt;progress()&lt;/code&gt;. From first principles. With a Sass wrapper. I sat with that for a while.&lt;/p&gt;

&lt;p&gt;While I was at it I found &lt;a href="https://github.com/lunelson/sass-lerp" rel="noopener noreferrer"&gt;&lt;code&gt;sass-lerp&lt;/code&gt;&lt;/a&gt;, which has done calc-based interpolation in Sass for years, and posts from &lt;a href="https://www.madebymike.com.au/writing/interpolation-without-animation/" rel="noopener noreferrer"&gt;MadeByMike&lt;/a&gt; and &lt;a href="https://typetura.com/blog/unit-division-with-css-and-fallbacks/" rel="noopener noreferrer"&gt;Typetura&lt;/a&gt; circling the same ground. The one piece I could still call mine was putting the halves together around a container unit and seeing that it opens every unit at once. I will take it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I shipped it anyway
&lt;/h2&gt;

&lt;p&gt;Two reasons, one boring and one that turned out to matter more.&lt;/p&gt;

&lt;p&gt;The boring one is support. Those version numbers mean browsers from 2026 onward. Everything older gets nothing from &lt;code&gt;progress()&lt;/code&gt;. The division trick reaches back to 2023. So the package declares the fallback and upgrades in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--cq-t&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;clamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="nt"&gt;cqi&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;320&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;960&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--cq-t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;320px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1280px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second reason is that &lt;code&gt;progress()&lt;/code&gt; hands you a 0 to 1 and stops there. The &lt;code&gt;calc(min + (max - min) * t)&lt;/code&gt; is still yours to write, in the right unit, with the right sign. My Sass functions take that arithmetic off your hands. You write &lt;code&gt;fluid-on(0.14s, 1.1s)&lt;/code&gt; and get the finished &lt;code&gt;calc()&lt;/code&gt; back: the difference worked out, the sign right when the range runs downhill, the unit carried over, a &lt;code&gt;rem&lt;/code&gt; mixed with a &lt;code&gt;px&lt;/code&gt; caught at compile time, and a bare &lt;code&gt;0&lt;/code&gt; turned into &lt;code&gt;0deg&lt;/code&gt; when the other end is an angle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part progress() cannot reach
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;progress()&lt;/code&gt; interpolates over whatever you give it. Give it &lt;code&gt;100vw&lt;/code&gt; and you have the viewport, same as the &lt;code&gt;clamp()&lt;/code&gt;-and-&lt;code&gt;vw&lt;/code&gt; genre. Give it &lt;code&gt;100cqi&lt;/code&gt; inside a container and the thing being measured changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxesc8l17fqm50qa27qak.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxesc8l17fqm50qa27qak.gif" alt="Dragging the sidebar rescales its contents while the window stays put" width="678" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That sidebar has a drag handle. The window never moves. Under cq-lerp the padding, the type size, the letter spacing and the weight of the links all follow the width of the sidebar itself. Under the conventional stylesheet nothing moves, because every viewport-based technique is reading a number that did not change.&lt;/p&gt;

&lt;p&gt;Same story with the three cards on the demo page: identical markup in a 240px, a 380px and a 520px slot, on one screen, at the same time. Each card sizes itself by its slot. A media query has nothing to key off, since all three see the same viewport.&lt;/p&gt;

&lt;p&gt;That is what stays useful no matter how far &lt;code&gt;progress()&lt;/code&gt; support spreads, and it is why the package has &lt;code&gt;cq&lt;/code&gt; in its name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Demo: &lt;a href="https://subamanis.github.io/cq-lerp/" rel="noopener noreferrer"&gt;subamanis.github.io/cq-lerp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/subamanis/cq-lerp" rel="noopener noreferrer"&gt;github.com/subamanis/cq-lerp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Package: &lt;code&gt;npm i cq-lerp&lt;/code&gt;, with a plain CSS entry at &lt;code&gt;cq-lerp/css&lt;/code&gt; if you do not use Sass&lt;/li&gt;
&lt;li&gt;Zeus, where the container-based fluid engine idea comes from: &lt;a href="https://www.zeuscss.com/" rel="noopener noreferrer"&gt;zeuscss.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>sass</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
