HTML & CSS - - By Hugo Giraudel

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.

Sponsors