{"id":6338,"date":"2025-06-30T12:30:02","date_gmt":"2025-06-30T17:30:02","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=6338"},"modified":"2025-06-30T12:30:03","modified_gmt":"2025-06-30T17:30:03","slug":"step-gradients-with-a-given-number-of-steps","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/step-gradients-with-a-given-number-of-steps\/","title":{"rendered":"Step Gradients with a Given Number of Steps"},"content":{"rendered":"\n<p>Let&#8217;s say we want some stepped gradients like the ones below, with a certain number of steps.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"767\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455796945-49b0fd56-3360-4508-a35c-f28bd195b4b0.png?resize=1024%2C767&#038;ssl=1\" alt=\"Desired_linear_result screenshot. Shows a bunch of linear gradients in 10 steps from left to right.\" class=\"wp-image-6341\" style=\"width:603px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455796945-49b0fd56-3360-4508-a35c-f28bd195b4b0.png?resize=1024%2C767&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455796945-49b0fd56-3360-4508-a35c-f28bd195b4b0.png?resize=300%2C225&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455796945-49b0fd56-3360-4508-a35c-f28bd195b4b0.png?resize=768%2C575&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455796945-49b0fd56-3360-4508-a35c-f28bd195b4b0.png?w=1080&amp;ssl=1 1080w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">the desired result<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Before reading further, try thinking about this. You are only given the start and end steps and the rest should be obtained via linear interpolation. How would you create them? Maybe put together a quick demo.<\/p>\n\n\n\n<p>Note that this is a different problem from stepped gradients with\u00a0<a href=\"https:\/\/stackoverflow.com\/q\/22052984\/1397351\">a certain step size<\/a>. The given step size problem is way more complex and it&#8217;s impossible to solve with CSS alone as long as we don\u2019t have native looping. SVG filters <a href=\"https:\/\/codepen.io\/thebabydino\/pen\/gbpENPm\">provide a solution<\/a>, though they limit us to using just pixel values for the step size; having\u00a0<code>em<\/code> or\u00a0<code>calc()<\/code> values there isn&#8217;t possible without JS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-classic-css-approach\">The Classic CSS Approach<\/h2>\n\n\n\n<p>This means computing the intermediate step values and specifying a stop and positions for each step. Something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    #<span class=\"hljs-number\">00272<\/span>b <span class=\"hljs-number\">10%<\/span>,\n    #<span class=\"hljs-number\">193<\/span>f2f <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">20%<\/span>,\n    #<span class=\"hljs-number\">325833<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">30%<\/span>,\n    #<span class=\"hljs-number\">4<\/span>b6f36 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">40%<\/span>,\n    #<span class=\"hljs-number\">64873<\/span>b <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">50%<\/span>,\n    #<span class=\"hljs-number\">7<\/span>c9f3f <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">60%<\/span>,\n    #<span class=\"hljs-number\">95<\/span>b743 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">70%<\/span>,\n    #aecf47 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">80%<\/span>,\n    #c7e74b <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">90%<\/span>,\n    #e0ff4f <span class=\"hljs-number\">0<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Tedious!<\/p>\n\n\n\n<p>And this is the simplified version, the one that <a href=\"https:\/\/css-tricks.com\/while-you-werent-looking-css-gradients-got-better\/\">avoids repeating stops and stop positions<\/a>, something that has been well-supported cross-browser for over half a decade, by the way. Because I often see the ancient, inefficient version that duplicates everything:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    #<span class=\"hljs-number\">00272<\/span>b <span class=\"hljs-number\">0%<\/span>,  #<span class=\"hljs-number\">00272<\/span>b <span class=\"hljs-number\">10%<\/span>,\n    #<span class=\"hljs-number\">193<\/span>f2f <span class=\"hljs-number\">10%<\/span>, #<span class=\"hljs-number\">193<\/span>f2f <span class=\"hljs-number\">20%<\/span>,\n    #<span class=\"hljs-number\">325833<\/span> <span class=\"hljs-number\">20%<\/span>, #<span class=\"hljs-number\">325833<\/span> <span class=\"hljs-number\">30%<\/span>,\n    #<span class=\"hljs-number\">4<\/span>b6f36 <span class=\"hljs-number\">30%<\/span>, #<span class=\"hljs-number\">4<\/span>b6f36 <span class=\"hljs-number\">40%<\/span>,\n    #<span class=\"hljs-number\">64873<\/span>b <span class=\"hljs-number\">40%<\/span>, #<span class=\"hljs-number\">64873<\/span>b <span class=\"hljs-number\">50%<\/span>,\n    #<span class=\"hljs-number\">7<\/span>c9f3f <span class=\"hljs-number\">50%<\/span>, #<span class=\"hljs-number\">7<\/span>c9f3f <span class=\"hljs-number\">60%<\/span>,\n    #<span class=\"hljs-number\">95<\/span>b743 <span class=\"hljs-number\">60%<\/span>, #<span class=\"hljs-number\">95<\/span>b743 <span class=\"hljs-number\">70%<\/span>,\n    #aecf47 <span class=\"hljs-number\">70%<\/span>, #aecf47 <span class=\"hljs-number\">80%<\/span>,\n    #c7e74b <span class=\"hljs-number\">80%<\/span>, #c7e74b <span class=\"hljs-number\">90%<\/span>,\n    #e0ff4f <span class=\"hljs-number\">90%<\/span>, #e0ff4f <span class=\"hljs-number\">100%<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We could generate the stop list using Sass with looping and the <a href=\"https:\/\/sass-lang.com\/documentation\/modules\/color\/#mix\"><code>mix()<\/code><\/a> function.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"SCSS\" data-shcb-language-slug=\"scss\"><span><code class=\"hljs language-scss\"><span class=\"hljs-comment\">\/\/ $c0: gradient start<\/span>\n<span class=\"hljs-comment\">\/\/ $c1: gradient end<\/span>\n<span class=\"hljs-comment\">\/\/ $n: number of steps<\/span>\n<span class=\"hljs-keyword\">@function<\/span> stop-list(<span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$n<\/span>) {\n  <span class=\"hljs-variable\">$l<\/span>: (); <span class=\"hljs-comment\">\/\/ list of stops, initially empty<\/span>\n\n  <span class=\"hljs-keyword\">@for<\/span> <span class=\"hljs-variable\">$i<\/span> from <span class=\"hljs-number\">0<\/span> to <span class=\"hljs-variable\">$n<\/span> {\n    <span class=\"hljs-variable\">$l<\/span>: <span class=\"hljs-variable\">$l<\/span>, mix(<span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$i<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>)) <span class=\"hljs-number\">0<\/span> (<span class=\"hljs-variable\">$i<\/span> + <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">100%<\/span> \/ <span class=\"hljs-variable\">$n<\/span>;\n  }\n\n  <span class=\"hljs-keyword\">@return<\/span> <span class=\"hljs-variable\">$l<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: linear-gradient(<span class=\"hljs-number\">90deg<\/span>, stop-list(<span class=\"hljs-number\">#00272b<\/span>, <span class=\"hljs-number\">#e0ff4f<\/span>, <span class=\"hljs-number\">10<\/span>));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SCSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">scss<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This produces the following compiled result:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    #<span class=\"hljs-number\">00272<\/span>b <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">10%<\/span>,\n    #<span class=\"hljs-number\">193<\/span>f2f <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">20%<\/span>,\n    #<span class=\"hljs-number\">325833<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">30%<\/span>,\n    #<span class=\"hljs-number\">4<\/span>b6f36 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">40%<\/span>,\n    #<span class=\"hljs-number\">64873<\/span>b <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">50%<\/span>,\n    #<span class=\"hljs-number\">7<\/span>c9f3f <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">60%<\/span>,\n    #<span class=\"hljs-number\">95<\/span>b743 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">70%<\/span>,\n    #aecf47 <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">80%<\/span>,\n    #c7e74b <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">90%<\/span>,\n    #e0ff4f <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">100%<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Not bad \u2014 but we should probably tweak that generating function a bit so we get rid of the unnecessary <code>0<\/code> and <code>100%<\/code> stop positions at the very start and at the very end and to add rounding in case <code>100<\/code> is not a multiple of <code>$n<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"SCSS\" data-shcb-language-slug=\"scss\"><span><code class=\"hljs language-scss shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c0: gradient start<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c1: gradient end<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number of steps<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> stop-list(<span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$n<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-variable\">$l<\/span>: (); <span class=\"hljs-comment\">\/\/ list of stops, initially empty<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@for<\/span> <span class=\"hljs-variable\">$i<\/span> from <span class=\"hljs-number\">0<\/span> to <span class=\"hljs-variable\">$n<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-variable\">$l<\/span>: <span class=\"hljs-variable\">$l<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      mix(<span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$i<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>))\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">1s<\/span>t stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very first stop\n<\/span><\/span><mark class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &gt; <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">0<\/span>, unquote(<span class=\"hljs-string\">\"\"<\/span>))\n<\/span><\/mark><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">2<\/span>nd stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very last stop\n<\/span><\/span><mark class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &lt; <span class=\"hljs-variable\">$n<\/span>, round((<span class=\"hljs-variable\">$i<\/span> + <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">100%<\/span> \/ <span class=\"hljs-variable\">$n<\/span>), unquote(<span class=\"hljs-string\">\"\"<\/span>));\n<\/span><\/mark><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> <span class=\"hljs-variable\">$l<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SCSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">scss<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Much better \u2014 but the Sass function doesn&#8217;t look pretty and it gets even more complex if we need to round those percentages to a certain precision <code>$p<\/code>, a certain number of decimals, not just to integer percentage values:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"SCSS\" data-shcb-language-slug=\"scss\"><span><code class=\"hljs language-scss shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">@use<\/span> <span class=\"hljs-string\">\"sass:math\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c0: gradient start<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c1: gradient end<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number of steps<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $p: rounding precision, how many decimals to keep<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> stop-list(<span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>: <span class=\"hljs-number\">2<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-variable\">$l<\/span>: (); <span class=\"hljs-comment\">\/\/ list of stops, initially empty<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@for<\/span> <span class=\"hljs-variable\">$i<\/span> from <span class=\"hljs-number\">0<\/span> to <span class=\"hljs-variable\">$n<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-variable\">$l<\/span>: <span class=\"hljs-variable\">$l<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      mix(<span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$i<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>))\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">1s<\/span>t stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very first stop\n<\/span><\/span><span class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &gt; <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">0<\/span>, unquote(<span class=\"hljs-string\">\"\"<\/span>))\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">2<\/span>nd stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very last stop\n<\/span><\/span><mark class='shcb-loc'><span>      if(\n<\/span><\/mark><mark class='shcb-loc'><span>        <span class=\"hljs-variable\">$i<\/span> &lt; <span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>,\n<\/span><\/mark><mark class='shcb-loc'><span>        round((<span class=\"hljs-variable\">$i<\/span> + <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">100%<\/span> \/ <span class=\"hljs-variable\">$n<\/span> * math.pow(<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-variable\">$p<\/span>)) * math.pow(<span class=\"hljs-number\">10<\/span>, -<span class=\"hljs-variable\">$p<\/span>),\n<\/span><\/mark><mark class='shcb-loc'><span>        unquote(<span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/mark><mark class='shcb-loc'><span>      );\n<\/span><\/mark><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> <span class=\"hljs-variable\">$l<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SCSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">scss<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_xbxeyOM\" src=\"\/\/codepen.io\/anon\/embed\/xbxeyOM?height=450&amp;theme-id=1&amp;slug-hash=xbxeyOM&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed xbxeyOM\" title=\"CodePen Embed xbxeyOM\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We still have that long list of values in the compiled CSS and, if we have 7 elements like this with stepped gradients, each is going to get its own long list.<\/p>\n\n\n\n<p>Another problem with this is we cannot tweak the stepped gradient from DevTools. Not in a non-tedious way that doesn&#8217;t involve changing almost every step manually. If we want to change one of the end steps from DevTools, we have to also change all the Sass-computed intermediate steps.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"543\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/458248325-2acac3da-a0e4-49b5-a1e8-02da911521c3.png?resize=1024%2C543&#038;ssl=1\" alt=\"Screenshot showing the Sass code that generates the steps and the visual result. On the right, a DevTools panel is open and we're modifying the first step in the compiled code the browser can see, but this doesn't change the Sass-generated intermediate ones.\" class=\"wp-image-6344\" style=\"width:767px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/458248325-2acac3da-a0e4-49b5-a1e8-02da911521c3.png?resize=1024%2C543&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/458248325-2acac3da-a0e4-49b5-a1e8-02da911521c3.png?resize=300%2C159&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/458248325-2acac3da-a0e4-49b5-a1e8-02da911521c3.png?resize=768%2C407&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/458248325-2acac3da-a0e4-49b5-a1e8-02da911521c3.png?w=1280&amp;ssl=1 1280w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">what happens when we change one end step from DevTools<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Using CSS variables can get around both these problems, but we cannot use CSS variable values inside the Sass <code>mix()<\/code> function. In order to use CSS variables, we have to use CSS <code>color-mix()<\/code> function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"SCSS\" data-shcb-language-slug=\"scss\"><span><code class=\"hljs language-scss shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">@use<\/span> <span class=\"hljs-string\">\"sass:math\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number to round to a certain precision<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $p: rounding precision, how many decimals to keep<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> round-to(<span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>: <span class=\"hljs-number\">2<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> round(<span class=\"hljs-variable\">$n<\/span> * math.pow(<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-variable\">$p<\/span>)) * math.pow(<span class=\"hljs-number\">10<\/span>, -<span class=\"hljs-variable\">$p<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c0: gradient start<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c1: gradient end<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number of steps<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $p: rounding precision, how many decimals to keep<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> stop-list(<span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>: <span class=\"hljs-number\">2<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-variable\">$l<\/span>: (); <span class=\"hljs-comment\">\/\/ list of stops, initially empty<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@for<\/span> <span class=\"hljs-variable\">$i<\/span> from <span class=\"hljs-number\">0<\/span> to <span class=\"hljs-variable\">$n<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-variable\">$l<\/span>: <span class=\"hljs-variable\">$l<\/span>,\n<\/span><\/span><mark class='shcb-loc'><span>      color-mix(in srgb, <span class=\"hljs-variable\">$c1<\/span> round-to(<span class=\"hljs-variable\">$i<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>), <span class=\"hljs-variable\">$p<\/span>), <span class=\"hljs-variable\">$c0<\/span>)\n<\/span><\/mark><span class='shcb-loc'><span>        \/\/ <span class=\"hljs-number\">1s<\/span>t stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very first stop\n<\/span><\/span><span class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &gt; <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">0<\/span>, unquote(<span class=\"hljs-string\">\"\"<\/span>))\n<\/span><\/span><span class='shcb-loc'><span>        \/\/ <span class=\"hljs-number\">2<\/span>nd stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very last stop\n<\/span><\/span><span class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &lt; <span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>, round-to((<span class=\"hljs-variable\">$i<\/span> + <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">100%<\/span> \/ <span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>), unquote(<span class=\"hljs-string\">\"\"<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> <span class=\"hljs-variable\">$l<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SCSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">scss<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Which produces the following ugly compiled CSS:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    color-mix(in srgb, var(--c1) <span class=\"hljs-number\">0%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">10%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">11.11%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">20%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">22.22%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">30%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">33.33%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">40%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">44.44%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">50%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">55.56%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">60%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">66.67%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">70%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">77.78%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">80%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">88.89%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">90%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">100%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We can tweak the Sass to get rid of the first and last <code>color-mix()<\/code> and use the given ends <code>--c0<\/code> and <code>--c1<\/code> instead (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/bNdjKwJ\">live demo<\/a>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"SCSS\" data-shcb-language-slug=\"scss\"><span><code class=\"hljs language-scss shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">@use<\/span> <span class=\"hljs-string\">\"sass:math\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number to round to a certain precision<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $p: rounding precision, how many decimals to keep<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> round-to(<span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>: <span class=\"hljs-number\">2<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> round(<span class=\"hljs-variable\">$n<\/span> * math.pow(<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-variable\">$p<\/span>)) * math.pow(<span class=\"hljs-number\">10<\/span>, -<span class=\"hljs-variable\">$p<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c0: gradient start<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $c1: gradient end<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $n: number of steps<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ $p: rounding precision, how many decimals to keep<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@function<\/span> stop-list(<span class=\"hljs-variable\">$c0<\/span>, <span class=\"hljs-variable\">$c1<\/span>, <span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>: <span class=\"hljs-number\">2<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-variable\">$l<\/span>: (); <span class=\"hljs-comment\">\/\/ list of stops, initially empty<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@for<\/span> <span class=\"hljs-variable\">$i<\/span> from <span class=\"hljs-number\">0<\/span> to <span class=\"hljs-variable\">$n<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-variable\">$l<\/span>: <span class=\"hljs-variable\">$l<\/span>,\n<\/span><\/span><mark class='shcb-loc'><span>      if(\n<\/span><\/mark><mark class='shcb-loc'><span>          <span class=\"hljs-variable\">$i<\/span> &gt; <span class=\"hljs-number\">0<\/span>,\n<\/span><\/mark><mark class='shcb-loc'><span>          if(\n<\/span><\/mark><mark class='shcb-loc'><span>            <span class=\"hljs-variable\">$i<\/span> &lt; <span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>,\n<\/span><\/mark><mark class='shcb-loc'><span>            color-mix(in srgb, <span class=\"hljs-variable\">$c1<\/span> round-to(<span class=\"hljs-variable\">$i<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>), <span class=\"hljs-variable\">$p<\/span>), <span class=\"hljs-variable\">$c0<\/span>),\n<\/span><\/mark><mark class='shcb-loc'><span>            <span class=\"hljs-variable\">$c1<\/span>\n<\/span><\/mark><mark class='shcb-loc'><span>          ),\n<\/span><\/mark><mark class='shcb-loc'><span>          <span class=\"hljs-variable\">$c0<\/span>\n<\/span><\/mark><mark class='shcb-loc'><span>        )\n<\/span><\/mark><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">1s<\/span>t stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very first stop\n<\/span><\/span><span class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &gt; <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">0<\/span>, unquote(<span class=\"hljs-string\">\"\"<\/span>))\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ <span class=\"hljs-number\">2<\/span>nd stop position for each stop\n<\/span><\/span><span class='shcb-loc'><span>      \/\/ not set (empty <span class=\"hljs-string\">''<\/span>) for very last stop\n<\/span><\/span><span class='shcb-loc'><span>      if(<span class=\"hljs-variable\">$i<\/span> &lt; <span class=\"hljs-variable\">$n<\/span> - <span class=\"hljs-number\">1<\/span>, round-to((<span class=\"hljs-variable\">$i<\/span> + <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">100%<\/span> \/ <span class=\"hljs-variable\">$n<\/span>, <span class=\"hljs-variable\">$p<\/span>), unquote(<span class=\"hljs-string\">\"\"<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">@return<\/span> <span class=\"hljs-variable\">$l<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SCSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">scss<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But the generated CSS still looks ugly and difficult to read:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    var(--c0) <span class=\"hljs-number\">10%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">11.11%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">20%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">22.22%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">30%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">33.33%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">40%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">44.44%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">50%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">55.56%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">60%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">66.67%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">70%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">77.78%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">80%<\/span>,\n    <span class=\"hljs-built_in\">color-mix<\/span>(in srgb, var(--c1) <span class=\"hljs-number\">88.89%<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c0)) <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">90%<\/span>,\n    <span class=\"hljs-built_in\">var<\/span>(--c1) <span class=\"hljs-number\">0<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So&#8230; isn&#8217;t there another way?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-no-support-ideas\">The No-Support Ideas<\/h2>\n\n\n\n<p>The spec defines a <a href=\"https:\/\/www.w3.org\/TR\/css-images-4\/#stripes\"><code>stripes()<\/code> image function<\/a> and my first thought was it should allow us to do this, though it was not clear to me in which direction the stripes would go, if we have a way of specifying that:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">stripes<\/span>(\n    #<span class=\"hljs-number\">00272<\/span>b,\n    #<span class=\"hljs-number\">193<\/span>f2f,\n    #<span class=\"hljs-number\">325833<\/span>,\n    #<span class=\"hljs-number\">4<\/span>b6f36,\n    #<span class=\"hljs-number\">64873<\/span>b,\n    #<span class=\"hljs-number\">7<\/span>c9f3f,\n    #<span class=\"hljs-number\">95<\/span>b743,\n    #aecf47,\n    #c7e74b,\n    #e0ff4f\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But the more I read the first paragraph in the spec definition, the more it sounds like this wasn&#8217;t meant for backgrounds, but for stripes going along the direction of things like borders (including roundings) and text strokes. In this case, the line direction would be the &#8220;missing&#8221; direction.<\/p>\n\n\n\n<p>There&#8217;s also <a href=\"https:\/\/github.com\/w3c\/csswg-drafts\/issues\/1332\">a proposal<\/a> to add animation-like gradient easing, including a <code>steps()<\/code> function, though, just like in the case of the <code>stripes()<\/code> function, there&#8217;s a lot about this I don&#8217;t understand and the proposal doesn&#8217;t seem to have moved much lately.<\/p>\n\n\n\n<p>Since neither of these can be used today, what other solutions that we can currently use do we have?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"enter-the-svg-filter-enhancement\">Enter the SVG <code>filter<\/code> Enhancement<\/h2>\n\n\n\n<p>Wait, don&#8217;t run away screaming! I promise that, leaving aside small browser issues, the technique is actually simple and even has better support than the CSS gradient double stop position syntax!<\/p>\n\n\n\n<p>Let&#8217;s say we have a plain <code>black<\/code> to <code>red<\/code> gradient on an element that also gets an SVG <code>filter<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(<span class=\"hljs-number\">90deg<\/span>, #<span class=\"hljs-number\">000<\/span>, #f00);\n  <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step)\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We&#8217;ve picked this gradient in particular because it&#8217;s a gradient from <code>0%<\/code> to <code>100%<\/code> one one of the RGBA channels (in this case, the red channel <code>R<\/code>) while all other channel values stay constant. It could also be written as:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.step<\/span> {\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(<span class=\"hljs-number\">90deg<\/span>, rgb(<span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0%<\/span> \/ <span class=\"hljs-number\">1<\/span>), <span class=\"hljs-built_in\">rgb<\/span>(<span class=\"hljs-number\">100%<\/span> <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0%<\/span> \/ <span class=\"hljs-number\">1<\/span>));\n<\/span><\/mark><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step)\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Writing it like this makes it even more obvious that the green and blue channels (the second and third values in the <code>rgb()<\/code>) are zeroed everywhere all the time (before applying the <code>filter<\/code> and <em>after<\/em> too, as the SVG <code>filter<\/code>, which we&#8217;ll see in a second, doesn&#8217;t affect any channel other than the red one), while the alpha (the final value, the one after the slash in the <code>rgb()<\/code>) is maxed out everywhere all the time. <\/p>\n\n\n\n<p>So basically, we go from a <code>0%<\/code> red (which is equivalent to <code>black<\/code>) at the start on the left to a <code>100%<\/code> red (which is the same as the <code>red<\/code> keyword value) at the end on the right.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"137\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455271022-11851c25-7c8b-40e3-b088-a648afcc10a8-1.png?resize=1024%2C137&#038;ssl=1\" alt=\"a simple left to right black to red gradient band\" class=\"wp-image-6347\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455271022-11851c25-7c8b-40e3-b088-a648afcc10a8-1.png?resize=1024%2C137&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455271022-11851c25-7c8b-40e3-b088-a648afcc10a8-1.png?resize=300%2C40&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455271022-11851c25-7c8b-40e3-b088-a648afcc10a8-1.png?resize=768%2C102&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455271022-11851c25-7c8b-40e3-b088-a648afcc10a8-1.png?w=1080&amp;ssl=1 1080w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">our initial gradient<\/figcaption><\/figure>\n\n\n\n<p>This SVG <code>filter<\/code> needs to live inside an <code>svg<\/code> element. Since this <code>svg<\/code> element only exists to contain the <code>filter<\/code>, we don&#8217;t have any graphics that are going to be visible on the screen within it, it is functionally the same as a <code>style<\/code> element. So we zero its dimensions (<code>width<\/code> and <code>height<\/code> attributes), hide it from screen readers (<code>aria-hidden<\/code>) and take it out of the document flow (from the CSS).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'step'<\/span> <span class=\"hljs-attr\">color-interpolation-filters<\/span>=<span class=\"hljs-string\">'sRGB'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">svg<\/span><span class=\"hljs-selector-attr\">&#91;height=<span class=\"hljs-string\">'0'<\/span>]<\/span><span class=\"hljs-selector-attr\">&#91;aria-hidden=<span class=\"hljs-string\">'true'<\/span>]<\/span> { <span class=\"hljs-attribute\">position<\/span>: fixed }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>filter<\/code> element also gets another attribute other than the <code>id<\/code>. We aren&#8217;t going into it, just know we need to set it to <code>sRGB<\/code> for cross-browser compatibility <em><strong>if<\/strong><\/em> we mess with the RGB channels, as the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/color-interpolation-filters#formal_definition\">spec default<\/a> and the one used by all browsers nowadays is <code>linearRGB<\/code>, but <code>sRGB<\/code> is likely what we want in most cases, plus it used to be the <em>only<\/em> value that worked in Safari, though that has recently changed.<\/p>\n\n\n\n<p>In our particular case, if we don&#8217;t set the <code>color-interpolation-filters<\/code> attribute to <code>sRGB<\/code>, we won&#8217;t get equally sized steps in any browser other than older Safari versions which use <code>sRGB<\/code> anyway.<\/p>\n\n\n\n<p>Inside this <code>filter<\/code>, we have a <a href=\"https:\/\/webplatform.github.io\/docs\/svg\/elements\/feComponentTransfer\/#Table-and-Discrete-Component-Transfers\"><code>feComponentTransfer<\/code><\/a> primitive. This allows us to manipulate the RGBA channels individually (via the suggestively named <code>feFuncR<\/code>, <code>feFuncG<\/code>, <code>feFuncB<\/code> and <code>feFuncA<\/code>) in various ways. In this case, we have a gradient from&nbsp;<code>black (0%&nbsp;red)<\/code> to <code>red (100% red)<\/code> so we manipulate the red channel using <code>feFuncR<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'step'<\/span> <span class=\"hljs-attr\">color-interpolation-filters<\/span>=<span class=\"hljs-string\">'sRGB'<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feFuncR<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'discrete'<\/span> <span class=\"hljs-attr\">tableValues<\/span>=<span class=\"hljs-string\">'0 1'<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We&#8217;ve set the <code>type<\/code> of <code>feFuncR<\/code> to <code>discrete<\/code>, meaning the output red channel only has discrete values and these values are those specified by the <code>tableValues<\/code> attribute, so in our case here, <code>0<\/code> and <code>1<\/code> (<code>1<\/code> being the decimal representation of <code>100%<\/code>).<\/p>\n\n\n\n<p>What does this mean? Where the input value for the red channel is below <code>50%<\/code> (<code>.5<\/code>), so on the left half of the initial gradient, the <code>filter<\/code> output value for the red channel is <code>0<\/code> (zeroed). And where the input value for the red channel is at least <code>50%<\/code> (<code>.5<\/code>), so on the right half of the initial gradient, the <code>filter<\/code> output for the red channel is <code>1<\/code> (maxed out). Since the green and blue channels are zero everyehere, this makes the left half of our gradient a <code>black<\/code> step and the right half a <code>red<\/code> step.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"604\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810629-f253f4f3-6564-407c-89f8-722340e50c0d.png?resize=840%2C604&#038;ssl=1\" alt=\"Screenshot of the two step gradient example. Shows the filter input (the initial left to right, black to red gradient) and output (the filtered gradient, the one in two steps: black and red). On top of these gradients, we also have the boundary lines for the two intervals the [0%, 100%] interval of the red channel progression (which coincides with the input gradient) is split into. At the bottom, the feFuncA primitive creating this result is also shown.\" class=\"wp-image-6349\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810629-f253f4f3-6564-407c-89f8-722340e50c0d.png?w=840&amp;ssl=1 840w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810629-f253f4f3-6564-407c-89f8-722340e50c0d.png?resize=300%2C216&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810629-f253f4f3-6564-407c-89f8-722340e50c0d.png?resize=768%2C552&amp;ssl=1 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\">a black to red stepped gradient with 2 steps, before and after applying the step <code>filter<\/code><\/figcaption><\/figure>\n\n\n\n<p>Basically, when we have two values for <code>tableValues<\/code>, the <code>[0, 1]<\/code> interval of possible red input values gets split into two: <code>[0, .5)<\/code> and <code>[.5, 1]<\/code>. The first interval <code>[0, .5)<\/code> gets mapped to the first of the two <code>tableValues<\/code> and the second interval <code>[.5 1]<\/code> gets mapped to the second of the two <code>tableValues<\/code>.<\/p>\n\n\n\n<p>Now let&#8217;s say we add a <code>.5<\/code> value in between:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feFuncR<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'discrete'<\/span> <span class=\"hljs-attr\">tableValues<\/span>=<span class=\"hljs-string\">'0 .5 1'<\/span>\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This gives us three steps from left to right: a <code>0%<\/code> red (<code>black<\/code>), a <code>50%<\/code> red (<code>maroon<\/code>) and a <code>100%<\/code> red (<code>red<\/code>).<\/p>\n\n\n\n<p>Now that we have three values for <code>tableValues<\/code>, the <code>[0, 1]<\/code> interval of possible red input values gets split into three: <code>[0, .(3))<\/code>, <code>[.(3),.(6))<\/code> and <code>[.(6), 1]<\/code>. The first one, in our case being the left third of the gradient, gets mapped to <code>0<\/code>, so we have <code>0%<\/code> red (<code>black<\/code>) there. The second one, in our case being the middle third of the gradient, gets mapped to <code>.5<\/code>, so we have <code>50%<\/code> red (<code>marron<\/code>) there. The third one, in our case being the right third of the gradient, gets mapped to <code>1<\/code>, so we have <code>100%<\/code> red (<code>red<\/code>) there.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"604\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810668-c93bd651-e9d5-4381-842f-1b2bc09c955a.png?resize=840%2C604&#038;ssl=1\" alt=\"Screenshot of the three step gradient example. Shows the filter input (the initial left to right, black to red gradient) and output (the filtered gradient, the one in three steps: black, maroon and red). On top of these gradients, we also have the boundary lines for the three intervals the [0%, 100%] interval of the red channel progression (which coincides with the input gradient) is split into. At the bottom, the feFuncA primitive creating this result is also shown.\" class=\"wp-image-6350\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810668-c93bd651-e9d5-4381-842f-1b2bc09c955a.png?w=840&amp;ssl=1 840w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810668-c93bd651-e9d5-4381-842f-1b2bc09c955a.png?resize=300%2C216&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455810668-c93bd651-e9d5-4381-842f-1b2bc09c955a.png?resize=768%2C552&amp;ssl=1 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\">a black to red stepped gradient with 3 steps, before and after applying the step <code>filter<\/code><\/figcaption><\/figure>\n\n\n\n<p>We can also have four equally spaced values for <code>tableValues<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feFuncR<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'discrete'<\/span> <span class=\"hljs-attr\">tableValues<\/span>=<span class=\"hljs-string\">'0 .333 .667 1'<\/span>\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This gives us 4 steps from left to right:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"604\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455811396-c37b7633-eab8-4dc9-957b-0642d60b0410.png?resize=840%2C604&#038;ssl=1\" alt=\"Screenshot of the four step gradient example. Shows the filter input (the initial left to right, black to red gradient) and output (the filtered gradient, the one in four steps). On top of these gradients, we also have the boundary lines for the four intervals the [0%, 100%] interval of the red channel progression (which coincides with the input gradient) is split into. At the bottom, the feFuncA primitive creating this result is also shown.\" class=\"wp-image-6352\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455811396-c37b7633-eab8-4dc9-957b-0642d60b0410.png?w=840&amp;ssl=1 840w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455811396-c37b7633-eab8-4dc9-957b-0642d60b0410.png?resize=300%2C216&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455811396-c37b7633-eab8-4dc9-957b-0642d60b0410.png?resize=768%2C552&amp;ssl=1 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\">a black to red stepped gradient with 4 steps, before and after applying the step <code>filter<\/code><\/figcaption><\/figure>\n\n\n\n<p>In general, <code>n<\/code> equally spaced values for <code>tableValues<\/code> produce <code>n<\/code> equal steps for our <code>black<\/code> to <code>red<\/code> gradient:<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='246' src='https:\/\/videopress.com\/embed\/59nyPkSz?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t<figcaption>adjusting the number of steps adjusts tableValues and the visual result (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/NPWmorj\/\">live demo<\/a> &#8211; note that if you&#8217;re on a wide gamut display, you&#8217;re likely to see this <a href=\"https:\/\/issues.chromium.org\/issues\/373410239\">broken in Chrome<\/a>)<\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>If we use Pug, we can easily generate these values within a loop:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">- let a = new Array(n).fill(0).map((_, i) =&gt; +(i\/(n - 1)).toFixed(2));<br><br>svg(width='0' height='0' aria-hidden='true')<br>  filter#step(color-interpolation-filters='sRGB')<br>    feComponentTransfer<br>      feFuncR(type='discrete' tableValues=`${a.join(' ')}`)<\/pre>\n\n\n\n<p>Great, but this is a simple <code>black<\/code> to <code>red<\/code> gradient. How can we create a stepped orange to purple gradient, for example?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"extending-the-technique-different-palettes\">Extending the Technique: Different Palettes<\/h2>\n\n\n\n<p>This technique works for gradients where we vary any of the four RGBA channels from <code>0%<\/code> to <code>100%<\/code> along the gradient line while keeping all other three channels constant along the entire gradient line, though not necessarily zeroed or maxed out like in the <code>black<\/code> to <code>red<\/code> gradient example.<\/p>\n\n\n\n<p>For example, we could make the green channel <code>G<\/code> go from <code>0%<\/code> to <code>100%<\/code> along the gradient line, while the red channel is fixed at <code>26%<\/code> for the entire gradient, the blue channel is fixed at <code>91%<\/code> for the entire gradient and the alpha channel is fixed at <code>83%<\/code> for the entire gradient. This means we go from a slightly faded blue first step (<code>rgb(26% 0% 91%\/ 83%)<\/code>) to a somewhat desaturated aqua (<code>rgb(26% 100% 91%\/ 83%)<\/code>) for the final step.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"427\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455822110-9fe8edc0-7b78-4e50-a190-b0dc7254d08e.png?resize=840%2C427&#038;ssl=1\" alt=\"Screenshot of a five step gradient example where we vary the green channel while keeping all others constant along the entire gradient line. Shows the filter input (the initial left to right, faded blue to desaturated aqua gradient) and output (the filtered gradientin five steps). On top of these gradients, we also have the boundary lines for the five intervals the [0%, 100%] interval of the green channel progression (which coincides with the input gradient progression) is split into.\" class=\"wp-image-6355\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455822110-9fe8edc0-7b78-4e50-a190-b0dc7254d08e.png?w=840&amp;ssl=1 840w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455822110-9fe8edc0-7b78-4e50-a190-b0dc7254d08e.png?resize=300%2C153&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455822110-9fe8edc0-7b78-4e50-a190-b0dc7254d08e.png?resize=768%2C390&amp;ssl=1 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\">our example gradient, initial and with a 5 step filter applied<\/figcaption><\/figure>\n\n\n\n<p>Below you can see how you can play with an interactive demo that allows to create a custom 5 step gradient that has steps along one of the four channels while the others have a fixed value, custom set by us, but fixed for the entire gradient.<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='328' src='https:\/\/videopress.com\/embed\/OjK5Zpj7?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t<figcaption>picking the gradient channel and adjusting values for the fixed ones (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/ByNVrKL\/\">live demo<\/a> &#8211; note that this demo may also suffer from the Chrome wide gamut bug)<\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>Out of all these cases, the most interesting one is the one varying the alpha.<\/p>\n\n\n\n<p>First off, using the alpha channel allows us to avoid both the wide gamut bug and another <a href=\"https:\/\/issues.chromium.org\/issues\/425059618\">Chrome bug<\/a> we hit when using one of the RGB channels, <em>but not the alpha channel<\/em>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"725\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455843768-66471c4c-7bb3-42aa-bb2c-1ad6b52073ab.png?resize=900%2C725&#038;ssl=1\" alt=\"Chrome screenshot of four 5-step gradients obtained by varying each of the four RGBA channels while keeping all other constant. For those varying the RGB channels, the step edges are jagged, which doesn't happen when varying the alpha channel.\" class=\"wp-image-6358\" style=\"width:607px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455843768-66471c4c-7bb3-42aa-bb2c-1ad6b52073ab.png?w=900&amp;ssl=1 900w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455843768-66471c4c-7bb3-42aa-bb2c-1ad6b52073ab.png?resize=300%2C242&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/455843768-66471c4c-7bb3-42aa-bb2c-1ad6b52073ab.png?resize=768%2C619&amp;ssl=1 768w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><figcaption class=\"wp-element-caption\">the step edges are jagged in Chrome for the steps created on the RGB channels, but not on the alpha channel (<a href=\"https:\/\/codepen.io\/thebabydino\/pen\/dPojqGY\">live test<\/a>)<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Secondly, what this allows us to do is to fade <em>any<\/em> RGB value in steps along the entire alpha interval. And if we place this stepped fade on top of another solid RGB background, we get our desired stepped gradient where we only need to know the start (the layer underneath, seen exactly as set for the first step where the alpha of the stepped gradient above is <code>0<\/code>) and end step (used for the stepped gradient on top).<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_JodZwBb\" src=\"\/\/codepen.io\/anon\/embed\/JodZwBb?height=570&amp;theme-id=1&amp;slug-hash=JodZwBb&amp;default-tab=result\" height=\"570\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed JodZwBb\" title=\"CodePen Embed JodZwBb\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This is exactly how the gradients in the image at the start of the article were created. We have a <code>.step<\/code> element with a solid background set to the start step <code>--c0<\/code> and a pseudo fully covering it with a gradient where we vary the alpha of the end step <code>--c1<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">var<\/span>(--c0);\n\n  &amp;::before {\n    <span class=\"hljs-attribute\">position<\/span>: absolute;\n    <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(<span class=\"hljs-number\">90deg<\/span>, #<span class=\"hljs-number\">0000<\/span>, var(--c1));\n    <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step);\n    <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-string\">\"\"<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This pseudo has the alpha step <code>filter<\/code> applied.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\/\/- change this to change number of steps<br>- let n = 10;<br>- let a = new Array(n).fill(0).map((_, i) =&gt; +(i\/(n - 1)).toFixed(3));<br><br>svg(width='0' height='0' aria-hidden='true')<br>  filter#step<br>    feComponentTransfer<br>      feFuncA(type='discrete' tableValues=a.join(' '))<\/pre>\n\n\n\n<p>Note that in this case when we&#8217;re creating the steps on the alpha channel and we&#8217;re not touching the RGB channels, we don&#8217;t even need the <code>color-interpolation-filters<\/code> attribute anymore.<\/p>\n\n\n\n<p>You can check out the live demo for various <code>--c0<\/code> and <code>--c1<\/code> combinations below:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_jOoLmBv\" src=\"\/\/codepen.io\/anon\/embed\/jOoLmBv?height=450&amp;theme-id=1&amp;slug-hash=jOoLmBv&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed jOoLmBv\" title=\"CodePen Embed jOoLmBv\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>And yes, in case anyone is wondering, the pure CSS and the SVG <code>filter<\/code> results are identical &#8211; you can check it out in this demo.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OJaOxwz\" src=\"\/\/codepen.io\/anon\/embed\/OJaOxwz?height=450&amp;theme-id=1&amp;slug-hash=OJaOxwz&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OJaOxwz\" title=\"CodePen Embed OJaOxwz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"simplifying-the-technique-for-the-future\">Simplifying the Technique for the Future<\/h2>\n\n\n\n<p>It feels a bit inconvenient to use pseudo-elements for this instead of just having a <code>background<\/code>. The <code>filter()<\/code> <em>function<\/em> solves this problem. It takes an image (which can also be a CSS gradient) and a filter chain as inputs, then outputs the filtered image. This output can be used anywhere an image can be used in CSS \u2014 as a <code>background-image<\/code>, <code>mask-image<\/code>, <code>border-image<\/code>, even <code>shape-outside<\/code>!<\/p>\n\n\n\n<p>This way, our CSS can become:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: \n    <span class=\"hljs-built_in\">filter<\/span>(linear-gradient(<span class=\"hljs-number\">90deg<\/span>, #<span class=\"hljs-number\">0000<\/span>, var(--c1)), <span class=\"hljs-built_in\">url<\/span>(#step)) \n    <span class=\"hljs-built_in\">var<\/span>(--c0)\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Much simpler!<\/p>\n\n\n\n<p>The catch? While Safari has supported this for a decade (I first learned about this function and the Safari implementation in <a href=\"https:\/\/iamvdo.me\/en\/blog\/advanced-css-filters#filter\">the summer of 2015<\/a>!), <a href=\"https:\/\/github.com\/web-platform-tests\/interop\/issues\/717\">no other browser has followed<\/a> since. Here are the <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=541698\">Chrome<\/a> and <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1191043\">Firefox<\/a> bugs for anyone who wants to show interest and add to the use cases.<\/p>\n\n\n\n<p>Here is <a href=\"https:\/\/codepen.io\/thebabydino\/pen\/gbpZRBZ\">the <code>filter()<\/code> version<\/a> of the stepped gradients demo, but keep in mind it only works in Safari.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"extending-the-technique-back-and-forth-steps\">Extending the Technique: Back and Forth Steps<\/h2>\n\n\n\n<p>Now let&#8217;s say we wanted to modify our gradient to go back to <code>black<\/code> from <code>red<\/code> in the middle (we&#8217;re using the red and black gradient example here because of the contrast):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(<span class=\"hljs-number\">90deg<\/span>, #<span class=\"hljs-number\">000<\/span>, #f00, #<span class=\"hljs-number\">000<\/span>);\n  <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step)\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>filter<\/code> is generated exactly the same as before:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">- let n = 5;<br>- let a = new Array(n).fill(0).map((_, i) =&gt; +(i\/(n - 1)).toFixed(3));<br><br>svg(width='0' height='0' aria-hidden='true')<br>  filter#step(color-interpolation-filters='sRGB')<br>    feComponentTransfer<br>      feFuncR(type='discrete' tableValues=`${a.join(' ')}`)<\/pre>\n\n\n\n<p>This Pug is producing the following HTML:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'step'<\/span> <span class=\"hljs-attr\">color-interpolation-filters<\/span>=<span class=\"hljs-string\">'sRGB'<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feFuncR<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'func'<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'discrete'<\/span> <span class=\"hljs-attr\">tableValues<\/span>=<span class=\"hljs-string\">'0 .25 .5 .75 1'<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this case of a gradient going back, basically being reflected with respect to the middle, the middle red step is doubled when using the exact same red channel step gradient as before.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"364\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456574288-af19cbac-fafd-4334-ae07-ff0fbd9acf29.png?resize=1024%2C364&#038;ssl=1\" alt=\"Screenshot of a 2\u00b75 = 10 step gradient. Shows the filter input (the initial left to right, black to red and then back to black gradient) and output (the filtered gradient, in 5 + 5 steps). On top of these gradients, we also have the boundary lines for the intervals the [0%, 100%] interval of the red channel progression (which coincides with the first half of the input gradient) is split into. This simple linear gradient reflection results in a doubling of the middle red step.\" class=\"wp-image-6360\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456574288-af19cbac-fafd-4334-ae07-ff0fbd9acf29.png?resize=1024%2C364&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456574288-af19cbac-fafd-4334-ae07-ff0fbd9acf29.png?resize=300%2C107&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456574288-af19cbac-fafd-4334-ae07-ff0fbd9acf29.png?resize=768%2C273&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456574288-af19cbac-fafd-4334-ae07-ff0fbd9acf29.png?w=1280&amp;ssl=1 1280w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>This makes perfect sense. We&#8217;re reflecting the gradient and this repeats the <code>100%<\/code> red step in the middle. We don&#8217;t have <code>n<\/code> steps across the gradient line anymore, we have <code>2\u00b7n<\/code> of them, with the two in the middle having the same RGBA value (<code>100%<\/code> red).<\/p>\n\n\n\n<p>What we need to do is make it look like we only have <code>2\u00b7n - 1<\/code> steps by making the two steps in the middle half the size of the other ones. This means moving the cutoff to <code>100%<\/code> red (<code>(n - 1)\/n\u00b7100%<\/code> red, which is <code>80%<\/code> red in the <code>n = 5<\/code> example case here) at half a <code>100%\/(2\u00b7n - 1)<\/code> interval from the middle, both before and after. So our CSS becomes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-comment\">\/* cutoff to 100% red *\/<\/span>\n  <span class=\"hljs-attribute\">--r<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(calc((var(--n) - <span class=\"hljs-number\">1<\/span>) \/ <span class=\"hljs-built_in\">var<\/span>(--n) * <span class=\"hljs-number\">100%<\/span>), <span class=\"hljs-number\">0%<\/span>, <span class=\"hljs-number\">0%<\/span>);\n  <span class=\"hljs-comment\">\/* distance from the middle of gradient line *\/<\/span>\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">0.5<\/span> * <span class=\"hljs-number\">100%<\/span>\/ (<span class=\"hljs-number\">2<\/span> * <span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>);\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    #<span class=\"hljs-number\">000<\/span>,\n    var(--r) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> - var(--d)),\n    <span class=\"hljs-number\">#f00<\/span>,\n    <span class=\"hljs-built_in\">var<\/span>(--r) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> + var(--d)),\n    <span class=\"hljs-number\">#000<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This does the trick!<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"364\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456591236-3e9def57-7efe-4d8d-808b-49a6feef6b63.png?resize=1024%2C364&#038;ssl=1\" alt=\"Screenshot of a 2\u00b75 = 10 step gradient. Shows the filter input (the initial left to right, black to red and then back to black gradient) and output (the filtered gradient, in 5 + 5 steps). On top of these gradients, we also have the boundary lines for the intervals the [0%, 100%] interval of the red channel progression (which coincides with the first half of the input gradient) is split into. The trick here is that the two middle intervals aren't equal to all the others, but half of them. This way, the middle red step is still doubled, but it's also half the size of all other steps, so it doesn't look like it's repeated twice.\" class=\"wp-image-6363\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456591236-3e9def57-7efe-4d8d-808b-49a6feef6b63.png?resize=1024%2C364&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456591236-3e9def57-7efe-4d8d-808b-49a6feef6b63.png?resize=300%2C107&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456591236-3e9def57-7efe-4d8d-808b-49a6feef6b63.png?resize=768%2C273&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456591236-3e9def57-7efe-4d8d-808b-49a6feef6b63.png?w=1280&amp;ssl=1 1280w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>The stops we&#8217;re specifying in the CSS in the particular case of <code>n = 5<\/code> are the <code>0%<\/code> red (implicitly at the <code>0%<\/code> point of the gradient line), the <code>80%<\/code> red at the <code>44.(4)%<\/code> point of the gradient line (set explicitly), the <code>100%<\/code> red (implicitly at the <code>50%<\/code> point of the gradient line), the <code>80%<\/code> red at the <code>55.(5)%<\/code> point of the gradient line (also set explicitly) and the <code>0%<\/code> red (implicitly at the <code>100%<\/code> point of the gradient line).<\/p>\n\n\n\n<p>If we wanted to fine tune things, we could also simplify the middle offset computations:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">50% - d = <br>50% - .5\u00b7100%\/(2\u00b7n - 1) = <br>50% - 50%\/(2\u00b7n -1) = <br>50%\u00b7(1 - 1\/(2\u00b7n - 1)) = <br>50%\u00b7(1 - f)<\/pre>\n\n\n\n<p>So our CSS would become:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-comment\">\/* cutoff to 100% red *\/<\/span>\n  <span class=\"hljs-attribute\">--r<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(calc((var(--n) - <span class=\"hljs-number\">1<\/span>) \/ <span class=\"hljs-built_in\">var<\/span>(--n) * <span class=\"hljs-number\">100%<\/span>), <span class=\"hljs-number\">0%<\/span>, <span class=\"hljs-number\">0%<\/span>);\n  <span class=\"hljs-comment\">\/* fraction of distance from middle of gradient line *\/<\/span>\n  <span class=\"hljs-attribute\">--f<\/span>: <span class=\"hljs-number\">1<\/span>\/ (<span class=\"hljs-number\">2<\/span> * <span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>);\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n    <span class=\"hljs-number\">90deg<\/span>,\n    #<span class=\"hljs-number\">000<\/span>,\n    var(--r) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> - var(--f))),\n    <span class=\"hljs-number\">#f00<\/span>,\n    <span class=\"hljs-built_in\">var<\/span>(--r) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> + var(--f))),\n    <span class=\"hljs-number\">#000<\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that the SVG <code>filter<\/code> remains the exact same as before, we just pass the number of steps <code>n<\/code> to the CSS as a custom property:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">- let n = 5;<br><br>\/\/ exact same SVG filter as before<br>.step(style=`--n: ${n}`)<\/pre>\n\n\n\n<p>If we want our gradient to repeat and we don&#8217;t want a doubled end\/start step, we need to do something similar at the other end of the red scale (channel scale in general) and make it look as if the start\/end step is <code>100%\/(2\u00b7(n - 1))<\/code> of the gradient line (not <code>100%\/(2\u00b7n - 1)<\/code> like in the case of no gradient repetition reflection).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-comment\">\/* cutoff to 0% red *\/<\/span>\n  <span class=\"hljs-attribute\">--r0<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(calc(<span class=\"hljs-number\">1<\/span> \/ var(--n) * <span class=\"hljs-number\">100%<\/span>), <span class=\"hljs-number\">0%<\/span>, <span class=\"hljs-number\">0%<\/span>);\n  <span class=\"hljs-comment\">\/* cutoff to 100% red *\/<\/span>\n  <span class=\"hljs-attribute\">--r1<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(calc((var(--n) - <span class=\"hljs-number\">1<\/span>) \/ <span class=\"hljs-built_in\">var<\/span>(--n) * <span class=\"hljs-number\">100%<\/span>), <span class=\"hljs-number\">0%<\/span>, <span class=\"hljs-number\">0%<\/span>);\n  <span class=\"hljs-comment\">\/* fraction of distance from middle\/ end of gradient line *\/<\/span>\n  <span class=\"hljs-attribute\">--f<\/span>: <span class=\"hljs-number\">1<\/span>\/ (<span class=\"hljs-number\">2<\/span> * (<span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>));\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n      <span class=\"hljs-number\">90deg<\/span>,\n      #<span class=\"hljs-number\">000<\/span>,\n      var(--r0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * var(--f)),\n      <span class=\"hljs-built_in\">var<\/span>(--r1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> - var(--f))),\n      <span class=\"hljs-number\">#f00<\/span>,\n      <span class=\"hljs-built_in\">var<\/span>(--r1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> + var(--f))),\n      <span class=\"hljs-built_in\">var<\/span>(--r0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">2<\/span> - var(--f))),\n      <span class=\"hljs-number\">#000<\/span>\n    )\n    <span class=\"hljs-number\">0<\/span>\/ <span class=\"hljs-number\">50%<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that we&#8217;ve used a <code>background-size<\/code> of <code>50%<\/code>, which means <code>2<\/code> repetitions. For a generic number of repetitions <code>q<\/code>, our <code>background-size<\/code> is <code>100%\/q<\/code>.<\/p>\n\n\n\n<p>For the alpha channel variation that allows us to get any gradient from any <code>--c0<\/code> to any <code>--c1<\/code>, it&#8217;s very similar:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-comment\">\/* cutoff to 0% alpha *\/<\/span>\n  <span class=\"hljs-attribute\">--a0<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from var(--c1) r g b\/ <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">1<\/span> \/ var(--n)));\n  <span class=\"hljs-comment\">\/* cutoff to 100% alpha *\/<\/span>\n  <span class=\"hljs-attribute\">--a1<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from var(--c1) r g b\/ <span class=\"hljs-built_in\">calc<\/span>((var(--n) - <span class=\"hljs-number\">1<\/span>) \/ <span class=\"hljs-built_in\">var<\/span>(--n)));\n  <span class=\"hljs-comment\">\/* fraction of distance from middle\/ end of gradient line *\/<\/span>\n  <span class=\"hljs-attribute\">--f<\/span>: <span class=\"hljs-number\">1<\/span>\/ (<span class=\"hljs-number\">2<\/span> * (<span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>));\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">var<\/span>(--c0);\n\n  &amp;::before {\n    <span class=\"hljs-attribute\">position<\/span>: absolute;\n    <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n        <span class=\"hljs-number\">90deg<\/span>,\n        #<span class=\"hljs-number\">0000<\/span>,\n        var(--a0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * var(--f)),\n        <span class=\"hljs-built_in\">var<\/span>(--a1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> - var(--f))),\n        <span class=\"hljs-built_in\">var<\/span>(--c1),\n        <span class=\"hljs-built_in\">var<\/span>(--a1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> + var(--f))),\n        <span class=\"hljs-built_in\">var<\/span>(--a0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">2<\/span> - var(--f))),\n        <span class=\"hljs-number\">#0000<\/span>\n      )\n      <span class=\"hljs-number\">0<\/span> \/ <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">100%<\/span> \/ var(--q));\n    <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step);\n    <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-string\">\"\"<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can play with the demo below by changing the number of repetitions <code>q<\/code> to see how the result changes without needing to modify anything else.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_pvJOKOJ\" src=\"\/\/codepen.io\/anon\/embed\/pvJOKOJ?height=450&amp;theme-id=1&amp;slug-hash=pvJOKOJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed pvJOKOJ\" title=\"CodePen Embed pvJOKOJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>What if we wanted to have full steps at the start of the first repetition and at the end of last repetition? Well, in that case, given a number <code>q<\/code> of repetitions, we can compute the width of the lateral borders to be equal to half a step size on each side. A step size is <code>1\/(2\u00b7q\u00b7(n - 1) + 1)<\/code> of the pseudo parent&#8217;s <code>content-box<\/code> width, so the <code>border-width<\/code> on the pseudo needs to be half of that.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-comment\">\/* cutoff to 0% alpha *\/<\/span>\n  <span class=\"hljs-attribute\">--a0<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from var(--c1) r g b\/ <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">1<\/span> \/ var(--n)));\n  <span class=\"hljs-comment\">\/* cutoff to 100% alpha *\/<\/span>\n  <span class=\"hljs-attribute\">--a1<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from var(--c1) r g b\/ <span class=\"hljs-built_in\">calc<\/span>((var(--n) - <span class=\"hljs-number\">1<\/span>) \/ <span class=\"hljs-built_in\">var<\/span>(--n)));\n  <span class=\"hljs-comment\">\/* fraction of distance from middle\/ end of gradient line *\/<\/span>\n  <span class=\"hljs-attribute\">--f<\/span>: <span class=\"hljs-number\">1<\/span>\/ (<span class=\"hljs-number\">2<\/span> * (<span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>));\n  <span class=\"hljs-attribute\">container-type<\/span>: inline-size;\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">var<\/span>(--c0);\n\n  &amp;::before {\n    <span class=\"hljs-attribute\">position<\/span>: absolute;\n    <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">border<\/span>: solid <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">#0000<\/span>;\n    <span class=\"hljs-attribute\">border-width<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50<\/span>cqw \/ (<span class=\"hljs-number\">2<\/span> * var(--q) * (<span class=\"hljs-built_in\">var<\/span>(--n) - <span class=\"hljs-number\">1<\/span>) + <span class=\"hljs-number\">1<\/span>));\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(\n        <span class=\"hljs-number\">90deg<\/span>,\n        #<span class=\"hljs-number\">0000<\/span>,\n        var(--a0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * var(--f)),\n        <span class=\"hljs-built_in\">var<\/span>(--a1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> - var(--f))),\n        <span class=\"hljs-built_in\">var<\/span>(--c1),\n        <span class=\"hljs-built_in\">var<\/span>(--a1) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">1<\/span> + var(--f))),\n        <span class=\"hljs-built_in\">var<\/span>(--a0) <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50%<\/span> * (<span class=\"hljs-number\">2<\/span> - var(--f))),\n        <span class=\"hljs-number\">#0000<\/span>\n      )\n      <span class=\"hljs-number\">0<\/span> \/ <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">100%<\/span> \/ var(--q));\n    <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">url<\/span>(#step);\n    <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-string\">\"\"<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Modified interactive demo:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NPqLBNq\" src=\"\/\/codepen.io\/anon\/embed\/NPqLBNq?height=450&amp;theme-id=1&amp;slug-hash=NPqLBNq&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NPqLBNq\" title=\"CodePen Embed NPqLBNq\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>What makes this possible and easy is the fact that, by default, <code>background-size<\/code> and <code>background-position<\/code> are relative to the <code>padding-box<\/code> (their position is relative to the top left corner of the <code>padding-box<\/code>, so that <code>0<\/code> position is relative to the <code>padding-box<\/code> left edge and their size is relative to the <code>padding-box<\/code> dimensions, so that <code>100%<\/code> in <code>calc(100%\/var(--q))<\/code> is relative to the <code>padding-box<\/code> width), but extra <code>background<\/code> repetitions are painted in all directions under the <code>border<\/code> too.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"626\" height=\"562\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456658499-d7fd70ab-3c68-453f-a5fd-a17d380546cb.png?resize=626%2C562&#038;ssl=1\" alt=\"The box model and the default way backgrounds are painted. Their size and position is relative to the padding-box, but repetitions are also painted underneath the borders.\" class=\"wp-image-6364\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456658499-d7fd70ab-3c68-453f-a5fd-a17d380546cb.png?w=626&amp;ssl=1 626w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/456658499-d7fd70ab-3c68-453f-a5fd-a17d380546cb.png?resize=300%2C269&amp;ssl=1 300w\" sizes=\"auto, (max-width: 626px) 100vw, 626px\" \/><figcaption class=\"wp-element-caption\">by default, backgrounds cover the entire <code>border-box<\/code>, but start from the top left corner of the <code>padding-box<\/code><\/figcaption><\/figure>\n\n\n\n<p>Note that the whole reflect and repeat could be very much simplified on the CSS side if CSS gradients also allowed reflecting repetition <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/SVG\/Reference\/Attribute\/spreadMethod\">like SVG ones do<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"extending-the-technique-different-gradients\">Extending the Technique: Different Gradients<\/h2>\n\n\n\n<p>We&#8217;ve only used a left to right <code>linear-gradient()<\/code> so far, but the direction of the gradient may vary and we may well use a <code>radial-gradient()<\/code> or a <code>conic-gradient()<\/code> instead. Nothing changes about the <code>filter<\/code> in this case. The gradients below all use the exact same <code>filter<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_raVrZQN\" src=\"\/\/codepen.io\/anon\/embed\/raVrZQN?height=450&amp;theme-id=1&amp;slug-hash=raVrZQN&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raVrZQN\" title=\"CodePen Embed raVrZQN\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Regardless of the gradient type, the <code>filter()<\/code> function is going to simplify things if Chrome and Firefox implement it too. The relevant code for the demo above would become:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.step<\/span> {\n  <span class=\"hljs-attribute\">--c0<\/span>: <span class=\"hljs-number\">#00272b<\/span>;\n  <span class=\"hljs-attribute\">--c1<\/span>: <span class=\"hljs-number\">#e0ff4f<\/span>;\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">#0000<\/span>, <span class=\"hljs-built_in\">var<\/span>(--c1);\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">filter<\/span>(var(--img, conic-gradient(var(--s))), <span class=\"hljs-built_in\">url<\/span>(#step)) <span class=\"hljs-built_in\">var<\/span>(--c0);\n}\n\n<span class=\"hljs-selector-class\">.linear<\/span> {\n  <span class=\"hljs-attribute\">--img<\/span>: <span class=\"hljs-built_in\">linear-gradient<\/span>(to right bottom, var(--s));\n}\n<span class=\"hljs-selector-class\">.radial<\/span> {\n  <span class=\"hljs-attribute\">--img<\/span>: <span class=\"hljs-built_in\">radial-gradient<\/span>(circle, var(--s));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can check out <a href=\"https:\/\/codepen.io\/thebabydino\/pen\/XJbBPGb\">the live demo<\/a>, but remember it only works in Safari.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"refining-things\">Refining Things<\/h2>\n\n\n\n<p>The results aren&#8217;t perfect. When using radial or conic gradients or even linear ones at weird angles, we get jagged edges in between the steps. I guess it doesn&#8217;t look that bad in between steps that may be reasonably similar, but if we wanted to do something about it, what could we do?<\/p>\n\n\n\n<p>When creating the steps from the CSS, we can always use the <a href=\"https:\/\/mastodon.social\/@anatudor\/112553357846316566\"><code>1px<\/code> difference trick<\/a> (avoid using a <code>1%<\/code> difference for this, it <a href=\"https:\/\/mastodon.social\/@anatudor\/112965908316021981\">can be unreliable<\/a>) to smoothen things for radial and linear ones (the conic gradient case is a lot more complicated though and I haven&#8217;t been able to find a pure CSS solution that doesn&#8217;t involve emulating the conic gradient with a linear one).<\/p>\n\n\n\n<p>But what can we do about it in the case of steps created via an SVG <code>filter<\/code>?<\/p>\n\n\n\n<p>Given the introduction of the <code>1px<\/code> difference produces an effect similar to a tiny blur, the first instinct would be to try to blur the whole thing. However, the result looks terrible, even when we correct the edge alpha decrease, so the blur idea goes out the window!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_YPXOObZ\" src=\"\/\/codepen.io\/anon\/embed\/YPXOObZ?height=450&amp;theme-id=1&amp;slug-hash=YPXOObZ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed YPXOObZ\" title=\"CodePen Embed YPXOObZ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We could also smoothen the edges of each step using a technique similar to the <a href=\"https:\/\/mastodon.social\/@anatudor\/112523336154596358\">no matrix filter gooey effect<\/a>. That mostly works, save for a bit of weird rounding at the edges for all gradients and in the middle of the conic one. But that&#8217;s a lot of <code>filter<\/code> primitives, a lot for such a tiny visual gain.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MYwBxZG\" src=\"\/\/codepen.io\/anon\/embed\/MYwBxZG?height=450&amp;theme-id=1&amp;slug-hash=MYwBxZG&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MYwBxZG\" title=\"CodePen Embed MYwBxZG\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Another option would be to try to simplify this technique and smoothen the edges of even steps &#8211; this avoids increasing the number of primitives with the number of steps, but also comes with other technical challenges. So at the end of the day, it&#8217;s another path I&#8217;m not fully convinced it&#8217;s worth taking for such a small visual gain. Not to mention the weird edge rounding and the even more obvious clump in the middle of the <code>conic-gradient()<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OPVooKv\" src=\"\/\/codepen.io\/anon\/embed\/OPVooKv?height=450&amp;theme-id=1&amp;slug-hash=OPVooKv&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OPVooKv\" title=\"CodePen Embed OPVooKv\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Finally, we could make the gradients grainy. But the approach discussed in <a href=\"https:\/\/frontendmasters.com\/blog\/grainy-gradients\/\">a previous article<\/a> is likely not what we&#8217;re going for.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNdOrXW\" src=\"\/\/codepen.io\/anon\/embed\/bNdOrXW?height=450&amp;theme-id=1&amp;slug-hash=bNdOrXW&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNdOrXW\" title=\"CodePen Embed bNdOrXW\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>There may be cases where it is what we want, for example when it comes to such dithered band cards:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_WNVzBRY\" src=\"\/\/codepen.io\/anon\/embed\/WNVzBRY?height=450&amp;theme-id=1&amp;slug-hash=WNVzBRY&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed WNVzBRY\" title=\"CodePen Embed WNVzBRY\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Most of the time, this is probably not the desired result. So maybe try another approach to grainy gradients, one that doesn&#8217;t use displacement maps and also doesn&#8217;t alter the gradient palette?<\/p>\n\n\n\n<p>We could use the old approach of layering and blending with a desaturated noise layer whose alpha we also reduce to a certain extent before blending:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">'true'<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">filter<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">'grain'<\/span> <span class=\"hljs-attr\">x<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">y<\/span>=<span class=\"hljs-string\">'0'<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'1'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'1'<\/span> \n          <span class=\"hljs-attr\">color-interpolation-filters<\/span>=<span class=\"hljs-string\">'sRGB'<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feTurbulence<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'fractalNoise'<\/span> <span class=\"hljs-attr\">baseFrequency<\/span>=<span class=\"hljs-string\">'.713'<\/span> <span class=\"hljs-attr\">numOctaves<\/span>=<span class=\"hljs-string\">'3'<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feColorMatrix<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'saturate'<\/span> <span class=\"hljs-attr\">values<\/span>=<span class=\"hljs-string\">'0'<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feFuncA<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'linear'<\/span> <span class=\"hljs-attr\">slope<\/span>=<span class=\"hljs-string\">'.6'<\/span>\/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">feComponentTransfer<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">feBlend<\/span> <span class=\"hljs-attr\">in2<\/span>=<span class=\"hljs-string\">'SourceGraphic'<\/span> <span class=\"hljs-attr\">mode<\/span>=<span class=\"hljs-string\">'overlay'<\/span>\/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">filter<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we fully desaturate the noise produced by <code>feTurbulence<\/code> and then scale down its alpha (to <code>.6<\/code> of what it would be otherwise).<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_PwqyjVw\" src=\"\/\/codepen.io\/anon\/embed\/PwqyjVw?height=450&amp;theme-id=1&amp;slug-hash=PwqyjVw&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed PwqyjVw\" title=\"CodePen Embed PwqyjVw\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This is the path taken by the sunburst demo below, which was created taking inspiration from the heading image of <a href=\"https:\/\/frontendmasters.com\/blog\/css-bursts-with-conic-gradients\/\">an earlier post here<\/a>:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_pvoBeZP\" src=\"\/\/codepen.io\/anon\/embed\/pvoBeZP?height=450&amp;theme-id=1&amp;slug-hash=pvoBeZP&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed pvoBeZP\" title=\"CodePen Embed pvoBeZP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This comes with the disadvantage of altering the original palette, but if that&#8217;s not as much of an issue, it could work.<\/p>\n\n\n\n<p>Finally, another option would be XOR-ing the alpha of the desaturated and reduced alpha noise layer and the alpha of the steps:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MYwPvPP\" src=\"\/\/codepen.io\/anon\/embed\/MYwPvPP?height=450&amp;theme-id=1&amp;slug-hash=MYwPvPP&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MYwPvPP\" title=\"CodePen Embed MYwPvPP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A deep dive into producing an interpolated &#8220;gradient&#8221; of colors from just two provided colors.<\/p>\n","protected":false},"author":32,"featured_media":6385,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,201,360,91],"class_list":["post-6338","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-gradients","tag-steps","tag-svg"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/06\/Step-Gradients-with-a-Given-Number-of-Steps.jpg?fit=1140%2C676&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6338","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=6338"}],"version-history":[{"count":26,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6338\/revisions"}],"predecessor-version":[{"id":6403,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6338\/revisions\/6403"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/6385"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=6338"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=6338"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=6338"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}