Creating a Rainbow Border with Sass

A lot of modern designs are making good use of a multi-colored border effect (which we might also refer to as a “rainbow” border or “striped” border). For example, look at Treehouse’s blog – it has a multi-colored border under its navigation and it looks really nice.

Treehouse's colored border

However, in my opinion, Treehouse is coding it wrong. Since this is strictly concerned with aesthetics, it should be handled by CSS – and CSS only – without requiring any extra work in the HTML. Treehouse uses 50 span elements, each of them with a specific background color and a specific size, to create its nav border.

Whaaaat?

It’s overkill to dump such a large load of elements into the DOM just so you can create a striped border. Why not use CSS for this? I mean only CSS. Gradients are awesome for this type of task! Yes, you can create multiple colors in this manner using CSS gradients, and it’s quite easy to do.

The only caveat is that gradients are kind of a pain to write. They have a tedious syntax that is very likely to result in mistyping and errors. Plus having to repeat color stops in order to achieve multiple colors is annoying.

Sass to the Rescue!

This is something I wrote about last year on CSS-Tricks – but looking back at my code now, I thought it would be a good idea to revisit the whole thing to see if we can do better, especially since we now have access to Sass 3.3 features.

Before jumping into the code, let’s see what we want to create. I can see three different ways to produce this effect:

  1. Equally wide color sections based on a list of colors.
  2. Custom-sized color sections based on a 2-dimensional list of colors + color stops.
  3. Randomly-sized color sections based on a list of colors with randomly generated color stops.

What I would like to have is a unique function that defers to “private” functions depending on the case we encounter. This keeps the API simple for the user and the code well organized for the developer (AKA you).

The Core Function

@function stripes($colors, $direction: 90deg, $random: false) {
  // If lonely color
  @if length($colors) == 1 { @return $colors; }

  // Else
  $type: 'equal';

  @if $random {
    $type: 'random';
  }

  @else if length(nth($colors, 1)) > 1 {
    $type: 'custom';
  }

  @return linear-gradient($direction, call('_stripes-#{$type}-stops', $colors));
}

Here is what happens: first we check if the $colors variable is a single item, in which case we return it. Else, we define the case we are facing: random, custom or equal. If, like me, you prefer ternary operators, here is what you can do for the same result:

$type: if($random, 'random', if(length(nth($colors, 1)) > 1, 'custom', 'equal'));

Finally, we make good use of the call function from Sass 3.3. This makes it possible for us to invoke a function using its name as a string, meaning we can dynamically call the delayed function based on the defined $type.

Notice that we return a linear-gradient() directly so that calling the function is as easy as background: stripes($colors).

Note: if you are running Sass 3.2 and can’t use the call function, move the @return directives into the conditional statements.

Equal Color Stops

What is quite awesome about the linear-gradient() syntax is that it is based on a comma-separated list. This means a well formatted Sass list can be directly injected into the gradient function. All we have to do is build a comma separated list with each item being a 2-item spaced list consisting of the color and the color stop. For instance: red 10%, blue 10%.

Now let’s get back to our function. The easiest case is definitely one where all the colors are the same width. To define this width, all we have to do is divide 100% by the number of colors in the list. Then we loop through the list and build a clean list for the gradient function.

@function _stripes-equal-stops($colors) {
  $gradients: ();                                                        // 1
  $stops: 100% / length($colors);                                        // 2

  @for $i from 1 to length($colors) {                                    // 3
    $gradients: append($gradients, nth($colors, $i) $i * $stops, comma); // 4
    @if $i < length($colors) {                                        // 5
      $gradients: append($gradients, nth($colors, $i + 1) $i * $stops);  // 6
    }
  }

  @return $gradients;                                                    // 7
}

This may look complicated but it’s actually really simple. Here is what it does:

  1. Instantiate an empty list for our colors and color stops.
  2. Define the width of a color in percentage.
  3. Loop through the colors.
  4. Add the color (nth($colors, $i)) and its color stop ($i * $stops) to the $gradients list.
  5. If there is a color left in the list, append it the same way we just did.
  6. Finally, return the list.

For instance consider a $colors list as red, yellow, green. The result returned by the function will be red 33.33%, yellow 33.33%, yellow 66.66%, green 66.66% resulting in 3 equally-sized sections of color on our line.

Custom Color Stops

We can also accept a two-dimensional list of colors and color stops. Something like red 10%, blue 20%, green 60%. This means the red should take 10% of the total width, the blue should go from 10% to 20%, and the green from 20% to the end. Indeed, the last color stop is not parsed.

@function _stripes-custom-stops($colors) {
  $gradients: ();

  @for $i from 1 to length($colors) {
    @if length(nth($colors, $i)) > 1 {
      $color: nth(nth($colors, $i), 1);
      $stop:  nth(nth($colors, $i), 2);

      $gradients: append($gradients, $color $stop, comma);
      @if $i < length($colors) {
        $gradients: append($gradients, nth(nth($colors, $i + 1), 1) $stop);
      }
    }

    @else {
      @warn '`#{nth($colors, $i)}` skipped because it is only one item long while it should be 2: (color, color-stop).';
    }
  }

  @return $gradients;
}

The code is almost the same as in the previous function except instead of computing the color stop, we read it from the second item of the current loop index. In case there is no color stop defined for a color, we warn the user the color is being skipped.

Random Color Stops

Lastly, we can randomly generate our color stops. You may have noticed that in Treehouse’s example, the colors are not the same width; some are shorter, some are longer. If we want to achieve a similar effect, we could compute the color stops with a random function.

At first, I let fate decide the color stops, however I very often found myself facing a massive color taking up 90% of the width, and all the other colors struggling to fit into the remaining space. That sucked! What I wanted was near-equal-sized colors with a slight length variation.

@function _stripes-random-stops($colors) {
  @if length(nth($colors, 1)) > 1 {
    @return _stripes-custom-stops($colors);
  }

  $nb: length($colors);
  $gradients: ();
  $variation: 10;
  $median: 100 / $n;

  @for $i from 1 to $nb {
    $stop: $median * $i; 
    $random: rand($stop - $variation, $stop + $variation) * 1%;
    $gradients: append($gradients, nth($colors, $i) $random, comma);
    @if $i < length($colors) {
      $gradients: append($gradients, nth(nth($colors, $i + 1), 1) $random);
    } 
  }

  @return $gradients;
}

Pretty much the same code as before. But notice how we generate a random number between n - 10% and n + 10%, n being the average color width. This ensures that all color sections will be approximately the same width, without being strictly equal, resulting in a neat little random-sized-colors effect.

Both the random function in Sass 3.3 and the custom Ruby one from CodePen accept only a single argument: the maximum value in order to roll between 1 and the given value. So if we want a function to generate random min and max values, we can do it very easily like this:

@function rand($min, $max) {
  @return random($max - $min) + $min;
}

Final Words + Demo

So there you have a clean and concise function to generate a multi-colored border from a list of colors. As you can see, the code is not that hard to understand, and well organized in the end. The API is rather easy to use as well, so I think we nailed it!

You can play with the code on CodePen:

See the Pen DKhkf by SitePoint (@SitePoint) on CodePen.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Alex Walker

    Very cool example, Hugo.

    I didn’t know Sass had a random function. That’s got me thinking…

    • Craig Buckler

      Could a similar thing be done with your cicada principle?

      • Alex Walker

        Cicada is all about faking randomness, but yeah, for sure.

        I could see this being really fun with animation timing sequences.

  • Jingqi Xie

    It may be a good choice to make them in JavaScript.

    • http://hugogiraudel.com/ Hugo Giraudel

      I hardly think so.

    • Vinay Raghu

      Why do you think so?

      • Jingqi Xie

        It is easy to modify style sheets and DOM, and generate random numbers in JavaScript.

        • Vinay Raghu

          I think avoiding js as much as you can is a big performance gain. With sass the compilation is done dev time and its only the painting time you’re looking at. Whereas, with JS, you need to do the math in in the browser, which IMHO is uncool

        • http://hugogiraudel.com/ Hugo Giraudel

          Might be convenient but that doesn’t make it a good idea whatsoever. As Vinay said, JavaScript is costly. Sass is not.

  • Vinay Raghu

    Super awesome demo Hugo! Thanks!!

  • Craig Buckler

    Very clever, Hugo. Although it looks like quite a lot of complex Sass code to generate a relatively simple CSS3 background gradient! I also have a feeling an inline data-uri image would be fewer bytes, but your solution is more fun and flexible!

    • http://hugogiraudel.com/ Hugo Giraudel

      This is the kind of stuff you *code once and use everywhere*. Once you’ve set up this little API, using it is both easy and efficient, no matter how *complicated* the Sass code behind it might be.

      • Craig Buckler

        Yeah, possibly … although, if you’re like me, you’re forever improving and optimising “re-usable” code!

  • Oscar Blank

    Hey, this is great. I’ve only been using Sass for about 6 months, and love seeing this kind of article here on Sitepoint. More please!

  • Matt Soria

    This is awesome, really smart! Thanks! Has anyone else had the problem of getting the following error though? “Not a valid color stop: Sass::Script::String: call(“_stripes-random-stops”, #1abc9c #2ecc71 #3498db #9b59b6 #34495e #f1c40f #e67e22 #e74c3c #ecf0f1 #95a5a6))”

    • http://hugogiraudel.com/ Hugo Giraudel

      Yes. This is because you are using a space-separated list while `linear-gradient` needs a comma-separated list of color-stops.

      • Matt Soria

        Thanks!

  • http://hugogiraudel.com/ Hugo Giraudel

    There is absolutely no reason why this method (using linear-gradients) would not work on mobiles and tablets. I’ll go further saying there are high chances using gradients will work on mobile devices since iOS’ browser is Safari and latest Android browser is actually quite good. Opera Mini is still pretty shitty, agreed.

  • http://hugogiraudel.com/ Hugo Giraudel

    `linear-gradient` is a CSS value for `background-image`: http://dev.w3.org/csswg/css-images-3/#linear-gradients. If you want to make it work, you probably need to have a prefixed version as well. To do so, you can turn the function into a mixin, and make the mixin dump both the prefixed and unprefixed versions.

    • Evgeniy

      Thank you. Solved. When you use Compass, it run Sass version 3.2.17. In article above you used Sass 3.3. I’ve corrected your code (without “call” feature). It works fine. Great tutorial.

  • http://hugogiraudel.com/ Hugo Giraudel

    I’d say Safari needs vendor prefixes (`-webkit-`).