Why Do We Have `repeating-linear-gradient` Anyway?

It all started with this question Keith Clark recently asked on twitter.

CSS repeating-linear-gradients, do we need these? Can't the same thing be achieved with a linear-gradient and background-size?

That's a good question.

Their cousin, repeating radial gradients, definitely come in handy. They have saved me from having to write tens of stops inside a regular radial-gradient when I wanted to reproduce the look of a vinyl record with CSS.

Vinyl record with repeating-radial-gradient

But repeating-linear-gradient?

Until this year, I was also convinced they're useless. But in early February, while working on a canvas demo, I fell into the trap of styling the control panel for that demo and ended up reproducing over 100 slider designs I found online, each with the (self-imposed) restriction that I should use just one range input per slider.

Slider examples

Warning: while I did learn a lot creating them, I never went back to apply what I learned along the way to the sliders I had already coded, so, with the exception of the last few, I wouldn't really recommend them as a good resource. Also: if I never have to style another range input again, it will be too soon.

Some of these styled range inputs had striped fills or rulers, so they required using gradients, particularly repeating linear gradients, quite a bit. I used repeating linear gradients precisely because it turned out normal linear gradients were not enough. Let's see why!

We start by recreating the following pattern:

Simple stripes pattern

It has black and blue stripes, blue stripes being twice as wide as the black ones. If the black stripes have a width of .25em, the code to get this effect with repeating-linear-gradient is:

background: repeating-linear-gradient(135deg, 
  #000, #000 .25em /* black stripe */, 
  #0092b7 0, #0092b7 .75em /* blue stripe */
);

The gradient starts from the top left corner at an angle of 135°. If you need a refresher on how linear gradients and their angles work, check out this explanation (ignore the part about the old syntax, we have now left that behind). There's a black stripe from 0 to .25em along the gradient line, then a blue stripe from .25em to .75em (.75em - .25em = .5em = the blue stripe width = 2*.25em = twice the black stripe width). We can see it in action in this Pen:

See the Pen simple repeating-linear-gradient pattern by Ana Tudor (@thebabydino) on CodePen.

Now let's try to achieve the same with plain linear-gradient and background-size. We'll make the background a small square with a diagonal adding up to the width of a black stripe (.25em) and the width of a blue stripe (.5em). The diagonal of a square is the square edge times √2 and, if we know the diagonal is .25em + .5em = .75em, then the edge of the background-size square is going to be .75em/sqrt(2). This brings us to the following code:

background: linear-gradient(135deg, #000 .25em, #0092b7 0);
background-size: .75em/sqrt(2) .75em/sqrt(2);

Hmm, this doesn't look like what we wanted to achieve...

See the Pen simple linear-gradient pattern - step 1 by Ana Tudor (@thebabydino) on CodePen.

What if we put the .25em wide black stripe in the middle? Let's try that!

background: linear-gradient(135deg, 
  #0092b7 calc(50% - .125em) /* blue corner */, 
  #000 0, #000 calc(50% + .125em) /* black stripe */, 
  #0092b7 0 /* blue corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);

It's a little closer now, but it still doesn't look good.

See the Pen simple linear-gradient pattern - step 2 by Ana Tudor (@thebabydino) on CodePen.

If we zoom in, it becomes more obvious what the problem is and what we need to do about it. We have to fill out those corners with black.

Corner issue on zoom

This means adding a small black stripe at the beginning of the gradient line and one at the end. Each of these will be half the width of the initial black stripe in the middle (so half of .25em, which is .125em).

background: linear-gradient(135deg, 
  #000 .125em /* black corner */, 
  #0092b7 0, #0092b7 calc(50% - .125em) /* blue stripe */, 
  #000 0, #000 calc(50% + .125em) /* black stripe */, 
  #0092b7 0, #0092b7 calc(100% - .125em) /* blue stripe */, 
  #000 0 /* black corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);

It looks... slightly better?

See the Pen simple linear-gradient pattern - step 3 by Ana Tudor (@thebabydino) on CodePen.

It's still not what we want because now the blue stripes are too narrow. This is because the diagonal computations don't work out right anymore. Going from the top left corner to the bottom right corner, we now have half a black stripe (.125em), a blue stripe (.5em), a black stripe (.25em), another blue stripe (.5em) and another half a black stripe (.125em). This all adds up to a diagonal of 1.5em, which means we need to change the background-size to 1.5em/sqrt(2) 1.5em/sqrt(2):

See the Pen simple linear-gradient pattern - step 4 by Ana Tudor (@thebabydino) on CodePen.

Perfect! It's visually the same result as in the repeating-linear-gradient version. But in this case, it's too much work, too many computations, too much code, too much risk for rounding errors (the demo above will break if we set the font-size to some weird value like 1.734em which doesn't compute to an integer pixel value).

And in case you're not yet convinced that repeating-linear-gradient is the better solution in this case, let's imagine the gradient angle isn't 135°, but something different... let's say 120°.

Simple stripes pattern (120°)

Using repeating-linear-gradient, the code is almost the same as before, we only need to replace 135deg with 120deg.

See the Pen simple repeating-linear-gradient pattern v2 by Ana Tudor (@thebabydino) on CodePen.

All seems fine in Firefox and Edge/IE, but the transition from blue to black doesn't look good in Chrome. We can fix that by not making the transition sharp, leaving a 1px distance in between.

background: repeating-linear-gradient(120deg, 
  #0092b7 0, 
  #000 1px /* transition from previous blue stripe */, #000 .25em, 
  #0092b7 calc(.25em + 1px) /* from black to blue */, #0092b7 .75em
);

We use this Pen to test that this fixes the Chrome issue:

See the Pen simple repeating-linear-gradient pattern v2 (Chrome fix) by Ana Tudor (@thebabydino) on CodePen.

With plain old linear-gradient, we also need to change the angle from 135deg to 120deg. But it's not enough this time.

See the Pen simple linear-gradient pattern v2 - step 1 by Ana Tudor (@thebabydino) on CodePen.

We also need to change the dimensions of the background-size, as this is not a square anymore. The x dimension will be 1.5em*abs(cos(120deg)), while the y dimension will be 1.5em*abs(sin(120deg)).

See the Pen simple linear-gradient pattern v2 - step 2 by Ana Tudor (@thebabydino) on CodePen.

Again, the edges don't look good in Chrome, so we need to use the 1px spacing trick.

See the Pen simple linear-gradient pattern v2 (Chrome fix) by Ana Tudor (@thebabydino) on CodePen.

The linear-gradient alternative isn't pretty, but it exists.

Is there something repeating-linear-gradient can do that plain linear-gradient and the proper background-size can't do at all?

Well... there is!

If you've paid attention to the slider examples shown at the beginning, the striped gradient reproduced above is used to fill the slider track before the thumb (on the left of the thumb).

IE and Firefox have dedicated pseudo-elements for this (-ms-fill-lower and -moz-progress respectively) and all we need to do is add the background created earlier on these pseudos.

But for WebKit browsers, the only way to get that striped fill is to add another background on top of the regular one used for the slider track and then update how much of the track it covers as the slider thumb gets adjusted. Now this creates a big problem with the plain linear-gradient. We cannot control how much of the track it covers via background-size because we are already using background-size to create the striped look. In addition to that, gradients either repeat indefinitely or they don't repeat at all.

It would probably be cool if we could specify number values for background-repeat in the same way we can for animation-iteration-count. Something like background-repeat: .5 1.5 would make half of the background to be displayed horizontally and have one tile and a half displayed vertically. But we cannot do this. Maybe we have some other way of making a linear-gradient repeat just within certain limits?

Well, the only way we could create something that gets close to what we want with plain old linear-gradient and background-size is to stack up identical gradients and position them one after the other while not allowing them to repeat in at least one direction.

For example, if we wanted them to only cover a part of the element horizontally, we'd have to set background-repeat: repeat-y. The first gradient background would start horizontally at at 0*$size-x, the second at 1*$size-x and so on... where $size-x is the x component of the background-size. The following image illustrates how this would work:

Backgrounds positioned one after the other in a row

But this only works for changing how much of the track the striped part covers in increments of $size-x, it's useless if we want finer control. Plus, it's really ugly to stack up maybe 20 identical gradients.

background-image: 
  linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  ), 
  linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  ) /* repeat as many times as needed */;
;
background-position: 
  0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;

Ugh. Repeating the exact same gradient 20 times is awful. We could make the Sass better, but the generated CSS will still look ugly:

$grad: linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  );

background-image: $grad, $grad /* repeat as many times as needed */;
background-position: 
  0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;

The following Pen shows this method in action:

See the Pen constrain a linear-gradient within certain limits by Ana Tudor (@thebabydino) on CodePen.

In short, my advice is: don't do this! The code is horrible and breakable, we could easily end up with gaps between the gradients for certain background sizes or on zoom.

We have a much better method available. We can limit how much of the element a repeating-linear-gradient covers (no matter what the dimensions of the repeating part are) by using background-size and setting background-repeat to no-repeat:

See the Pen constrain a repeating-linear-gradient within certain limits by Ana Tudor (@thebabydino) on CodePen.

This is how I was able to control how much of the slider track was filled with the striped gradient every time the slider thumb value got updated - by changing the x component of the background-size.

The very same idea helps limit how much the rulers would extend in situations when we don't want them to extend across the whole length of the slider. Let's say we also want them to be horizontally in the middle, which makes the x component of background-position be 50%. This is how a very simple ruler would get created:

width: 19em;
background: 
  repeating-linear-gradient(90deg, 
      #c8c8c8, #c8c8c8 0.125em /* lines */, 
      transparent 0.125em, transparent 1.25em /* space between */
  ) 50% no-repeat;
background-size: 12.625em /* = 10*1.25em + .125em */ .5em;

And we can see the result in this pen:

See the Pen simple ruler not covering the entire element by Ana Tudor (@thebabydino) on CodePen.

In order to have both major and minor lines, we need to add a second repeating gradient, one that repeats at twice the interval for the first one and is twice as tall:

background: 
  repeating-linear-gradient(90deg, /* major */
      #c8c8c8, #c8c8c8 .125em /* lines */, 
      transparent , transparent 2.5em /* space between */
  ) 50% no-repeat, 
  repeating-linear-gradient(90deg, /* minor */
      #c8c8c8, #c8c8c8 .125em /* lines */, 
      transparent , transparent 1.25em /* space between */
  ) 50% no-repeat;
background-size: 12.625em 1em, 12.625em .5em;

The result can be seen in this Pen:

See the Pen ruler not covering the entire element #2 by Ana Tudor (@thebabydino) on CodePen.

We cold also tweak the y component of the background-position so that they align to top or to bottom. For example, we could do something like this:

background-position: right 50% bottom 2.25em;

This is exactly the kind of ruler that was used in one of the examples at the beginning.

See the Pen ruler not covering the entire element #3 by Ana Tudor (@thebabydino) on CodePen.

The slider thumb grips in the following example were created in exactly the same manner.

Thumb grips

There are two identical repeating linear gradients, both limited horizontally to twice the size of the repeating part plus the width of the non-transparent part. Horizontally, the first one is positioned at 25% and the second one at 75%.

background: 
  repeating-linear-gradient(90deg,
    #6b4c1e, #6b4c1e 1px, 
    #e1ba75 0, #e1ba75 2px, 
    transparent 0, transparent 4px
  ) 25% 50% /* left */, 
  repeating-linear-gradient(90deg,
    #6b4c1e, #6b4c1e 1px, 
    #e1ba75 0, #e1ba75 2px, 
    transparent 0, transparent 4px
  ) 75% 50% /* right */
  orange;
background-repeat: no-repeat;
background-size: 10px 10px;

But this is a very WET style of writing code. We could make it more maintainable using Sass:

$grip: repeating-linear-gradient(90deg,
  #6b4c1e, #6b4c1e 1px, 
  #e1ba75 0, #e1ba75 2px, 
  transparent 0, transparent 4px); /* we use this twice */

background: 
  $grip 25% 50% /* left */, 
  $grip 75% 50% /* right */
  orange;
background-repeat: no-repeat;
background-size: 10px 10px;

This gives us the following result:

See the Pen thumb grips by Ana Tudor (@thebabydino) on CodePen.

Repeating gradients used to be a real headache and we can still run into issues with thin gradient lines, especially when they cover larger areas. They won't always have the same width in WebKit browsers and Firefox on OS X isn't very precise when rendering them (though Firefox on Windows rarely ever fails here).

Issues with thin diagonal lines

But things have improved a lot and I believe they'll continue to improve. Even as I was creating those sliders at the beginning of this year, I could see pretty big differences between what were then the stable (41) and the canary (43) versions of Chrome.

Repeating gradients in Chrome 41 vs Chrome 43

Repeating gradients are a lot safer to use now and I think they deserve a bit more love because they can make our lives much easier and that makes them pretty cool in my eyes.