CSS

Rainbow Text with pure CSS

Published on 19 August 2023

Rainbow Text with pure CSS

In this blog post I’ll show you how to create a fun rolling rainbow text effect like this with just CSS! Although I warmly recommend also using a bit of HTML templating to make it easier to write the text.

If you’re just interested in the end result, you can skip to the end of the post to see the final code. I understand.

How

The HTML

To attain the desired effect, apply a CSS animation that transitions through rainbow colors, with a delayed start for scroll effect.

Split the input string to stagger the animation, making each letter an individual element. For example, simon becomes:

<span>
  <span>s</span>
  <span>i</span>
  <span>m</span>
  <span>o</span>
  <span>n</span>
</span>

While you are allowed to write this by hand, it’s not so fun. Here’s how you convert a string to a group of spans with JSX:

{[...text].map((letter, i) => {
    return letter === ' ' ? ' ' : <span style={`--delay: ${i*50}ms`}>{letter}</span>
})}

Spaces are left as is because they can’t change colour anyway.

On each span, a CSS variable is set that increases the start delay depending on the position of the letter in string. This is the magic that creates the scrolling effect.

The CSS

First thing that’s needed is a CSS animation that fades through the colours of the rainbow:

@keyframes rainbow {
  0% {
      color: #e81416;
  }

  16% {
      color: #ffa500;
  }

  33% {
      color: #faeb36;
  }

  50% {
      color: #79c314;
  }

  66% {
      color: #487de7;
  }

  83% {
      color: #4b369d;
  }

  92% {
      color: #70369d;
  }

  100% {
      color: #e81416;
  }
}

This animation starts and ends at red.

Next we’ll need to apply the animation to each span element, delaying the start time by the --delay variable.

.rainbow:hover span {
  animation: rainbow 5s infinite;
  animation-delay: var(--delay);
}

Putting it all together it looks like this:

HOVER FOR FUN

So it looks awesome, yay! But there is a slight problem… Because of the delay we added before, the letters are just chilling in white before the animation starts.

The fix is simple, just one character in fact. The neat thing about the animation delay is that you can delay it by negative amount.

In the context of looping animations, a negative delay essentially means the animation starts not from its initial state, but partway through. This is because it’s as if the animation has already been playing for the animation duration (5s) minus the delay (i * 50ms).

Let’s apply a negative delay instead:

<span style={`--delay: -${i * 50}ms`}>{letter}</span>

HOVER FOR FUN

Tuning Timings

The values I’ve provided are what I’ve found to work well.

It may be difficult to visually imagine the exact effect the animation speed vs the delay has, so here are two examples to help you out.

Delay

The following has a much higher delay of 500ms.

HOVER FOR FUN

As you can see this leads to more rainbow at a time.

Animation Duration

The following has a much faster animation of 1s.

HOVER FOR FUN

Gee willikers, that’s fast! And it makes a little nauseous, somehow.

Final code

If you just want to copy and paste the code, here’s the code for an Astro component.

Astro component

---
interface Props {
    text: string;
    element?: 'p' | 'span';
    speed?: number;
}

const { text, speed = 1, element: Element = 'span' } = Astro.props;
---
<style lang="scss">
@keyframes rainbow {
    0% {
        color: #e81416;
    }

    16% {
        color: #ffa500;
    }

    33% {
        color: #faeb36;
    }

    50% {
        color: #79c314;
    }

    66% {
        color: #487de7;
    }

    83% {
        color: #4b369d;
    }

    92% {
        color: #70369d;
    }

    100% {
        color: #e81416;
    }
}

.rainbow:hover span {
    animation: rainbow calc(10s * var(--speed)) infinite;
    animation-delay: var(--delay);
}
</style>


<Element style={`--speed: ${speed}`} class="rainbow">
    {[...text].map((letter, i) => {
        return letter === ' ' ? ' ' : <span style={`--delay: -${i*100}ms`}>{letter}</span>
    })}
</Element>

Couple other blog posts

Life
A colorful urban illustration with neon signboards in Asian characters. A man in formal wear focuses on his smartphone amidst silhouetted pedestrians, with modern skyscrapers in the background

29 October 2023

Moving Abroad: A Challenging yet Rewarding Endeavor

If you've ever thought about leaving home and moving abroad, you'll now how tough it can be. I've found it to be great, but not without challenges.

Stuff I Built
Bruh

13 August 2023

Automatic monitor brightness on Windows

After nearly losing my mind manually adjusting monitor brightness, I finally built an automatic solution

Artificial Intelligence

20 March 2023

Being a little bit silly with GPT-4 and ElevenLabs

Using modern Machine Learning models not for something useful, but for something silly instead