Creating Animated Gradient Texts (pure HTML and CSS!)
Tags: css, animation, webdev, html
Published: 2022-11-02 22:29:53

(You can find a video version of this article on [YouTube](https://www.youtube.com/watch?v=aWVEYYz90-A)! 📺)
{% youtube aWVEYYz90-A %}

> Check out an [interactive version of this blog](https://tomdohnal.com/posts/animated-gradient-headings) post on my own blog! (Includes custom widgets created to help you understand the key concepts faster!)

Let's learn how to create animated gradient texts (the ones you might know from [vercel.com](https://vercel.com)). This is what we'll build! 👇🏻


![animated heading saying animated. gradient. text.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbqxb7hkxp9sw4uv92e2.gif)
<br />

> We'll use plain HTML and CSS but feel free to use your favorite UI or styling
> library as you follow along!

## Creating gradient text (w/o animation)

First, we'll create a text with a gradient background but _no_ animation.

For starters, let's put some HTML markup to work with together and apply some boilerplate CSS styles:

```html
<head>
  <style>
    body {
      background: #000;
      padding: 64px;
      margin: 0;
      font-family: sans-serif;

      /* make the rendered glyphs a bit smoother around the edges */
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }

    h1 {
      margin: 0;
      font-size: 160px;
      font-weight: 800;
      color: #fff;
      letter-spacing: -0.03em;
    }

    .heading-line {
      line-height: 1;
      display: block;
      position: relative;
      text-align: center;
    }
  </style>
</head>

<body>
  <h1>
    <span class="heading-line heading-line-first">Animated.</span>
    <span class="heading-line heading-line-second">Gradient.</span>
    <span class="heading-line heading-line-third">Text.</span>
  </h1>
</body>
```

> If you'd like, you could use the fancy pants `nth-child(...)` selectors instead of `heading-line-first` etc. 😇

To add the gradients, we'll set the `color` to `transparent`, the `background-image` to `linear-gradient(...)` with our favorite colors, and `background-clip` to `text` to only render the gradient within the boundaries of the text. 👇🏻

```css
.heading-line {
  /* ... */

  /* set the color of the text to transparent */
  color: transparent;

  /* we need to clip the gradient background so that
  so only the text bit will have the gradient background
  (otherwise the entire rectangle the text is in would have it) */
  -webkit-background-clip: text;
  background-clip: text;
}

.heading-line-first {
  background-image: linear-gradient(to left, #ed8936, #ed64a6);
}

.heading-line-second {
  background-image: linear-gradient(to left, #38b2ac, #0bc5ea);
}

.heading-line-third {
  background-image: linear-gradient(to left, #ecc94b, #48bb78);
}
```

This is what you should end up with 👇🏻


![heading saying animated. gradient. text. with gradient background and no animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v15g8fae92m2zjt0w1p9.png)

## Animating the gradient

> In this section, we're only going to animate the _gradient_ background. To add some more spice to the animation, we're also going to animate _white_ background of each heading. That's gonna be covered in the next section though!

We now have a solid foundation to implement the animation!

Let's think about how the animation should look from a visual standpoint.

When the page loads, what colors should all the headings be? It should probably look like this, where the first heading has the gradient background and the rest have white backgrounds 👇🏻


![heading saying animated. gradient. text. the first line has a gradient background and the rest has white backgrounds](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zackrj9qpqwnfhp2zrrf.png)


<br />
<br />

Then, after a little while, the first gradient should start fading away and the second one should start fading in. 👇🏻


![heading saying animated. gradient. text. the first two lines has half gradient half white backgrounds and the third one has fully white background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miphrbqpodfmn1xfvzhv.png)

<br />
<br />

Finally, the first line should be white and the second should have the gradient. 👇🏻


![heading saying animated. gradient. text. the first and the third lines have gradient background while the second line has white background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9juduynrwee8n10w4lwp.png)

<br />

The same applies to the third line. Once that's finished, the first line's gradient appears again and the cycle restarts.

You can see how the gradients are fading in and out as we move the slider 👇🏻

![headings changing background colors from gradient to white as the slider moves](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2irtf5h0amln2mfyu6f.gif)

> To access the *interactive widget* you can play with, [view this article on my blog](https://tomdohnal.com/posts/animated-gradient-headings)

<br />
<br />

To make the animation, we want to divide it into 6 parts. In each part, either

- one heading will have the gradient background (the rest will have white background)
- two headings will be fading in/out

We can use the table below to know what exact behavior we want to model in CSS:

|Part|Heading 1|Heading 2|Heading 3|
|--- |--- |--- |--- |
|1|Gradient|No gradient|No gradient|
|2|Fading out|Fading in|No gradient|
|3|No gradient|Gradient|No gradient|
|4|No gradient|Fading out|Fading in|
|5|No gradient|No gradient|Gradient|
|6|Fading in|No gradient|Fading out|


<br />

We have now done most of the hard work. Now it's time to translate the table above into CSS.

The key thing we'll be animating is the `opacity` of the gradient background.

When we want the gradient to be visible, we'll set it to `1` and when we want it to be invisible, we'll set it to `0`.

Before we can do that, however, we need to make sure that there is a white background underneath the gradient.

To do that, we'll need to wrap the heading lines in `span` elements, add class `heading-line-gradient` to them and leverage the `::before` pseudo-element.

```html
<h1>
  <span class="heading-line heading-line-first">
    <span class="heading-line-gradient">Animated.</span>
  </span>
  <span class="heading-line heading-line-second">
    <span class="heading-line-gradient">Gradient.</span>
  </span>
  <span class="heading-line heading-line-third">
    <span class="heading-line-gradient">Text.</span>
  </span>
</h1>
```

The `.heading-line-gradient` will have the gradient background and `.heading-line::before` will have the white background.

```css
/* move these style from .heading-line 
   to .heading-line-gradient */
.heading-line-gradient {
  color: transparent;
  background-image: linear-gradient(to left, #ed8936, #ed64a6);
  -webkit-background-clip: text;
  background-clip: text;
}

/* move this from .heading-line-first */
.heading-line-first > .heading-line-gradient {
  background-image: linear-gradient(to left, #ed8936, #ed64a6);
}

/* move this from .heading-line-second */
.heading-line-second > .heading-line-gradient {
  background-image: linear-gradient(to left, #38b2ac, #0bc5ea);
}

/* move this from .heading-line-third */
.heading-line-third > .heading-line-gradient {
  background-image: linear-gradient(to left, #ecc94b, #48bb78);
}

/* white text underneath the gradient text */
.heading-line::before {
  line-height: 1;
  display: block;
  position: absolute;
  color: #fff;
  pointer-events: none;
  width: 100%;
}

.heading-line-first::before {
  content: "Animated.";
}

.heading-line-second::before {
  content: "Gradient.";
}

.heading-line-third::before {
  content: "Text.";
}
```

Finally, let's focus on the animation! We'll start with the first heading. We'll be animating the `opacity` of the `heading-line-gradient` class.

To define the animation, we'll use the `@keyframes` rule and we'll use the `animation` property to apply it.

We'll set the animation duration to `8s` and set `infinite` as the number of iterations. 👇🏻

```css
@keyframes first_heading_gradient {
  0%,
  /* 16.667 = 1/6 */
  16.667% {
    opacity: 1;
  }

  /* 33.333% = 2/6 */
  33.333%,
  /* 83.333% = 5/6 */
  83.333% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

.heading-line-first > .heading-line-gradient {
  /* ... */
  animation: first_heading_gradient 8s infinite;
}
```

We'll do the same for the second and third headings. 👇🏻

```css
@keyframes second_heading_gradient {
  0%,
  16.667% {
    opacity: 0;
  }

  33.333%,
  50% {
    opacity: 1;
  }

  66.667%,
  100% {
    opacity: 0;
  }
}

@keyframes third_heading_gradient {
  0%,
  50% {
    opacity: 0;
  }

  66.667%,
  83.333% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

.heading-line-second > .heading-line-gradient {
  /* ... */
  animation: second_heading_gradient 8s infinite;
}

.heading-line-third > .heading-line-gradient {
  /* ... */
  animation: third_heading_gradient 8s infinite;
}
```

This is what you should end up with 👇🏻


![heading saying animated. gradient. text. being animated.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33hn2c2q1amiorwpbe8y.gif)

## Animating white background

To make the animation stand out, we'll have to animate the white background of the heading as well.

Take one more look the what we want to achieve 👇🏻

![animated heading saying animated. gradient. text.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbqxb7hkxp9sw4uv92e2.gif)

<br />

You can notice that the white background is sort of fading in when the gradient is fading out and vice versa.

The effect is subtle, but it gives the animation a little bit more spice.

We can't, however, animate the white background for the whole duration of the gradient animation. That would look odd as the text would be almost invisible at one point.

Instead, we'll only start fading the white background away when the gradient is halfway through its fade-in animation and vice versa.

Play around with this widget to see how the opacity of the gradient and white background should behave 👇🏻

![one line of the heading being animated as the slider moves. as you move the slider, you can see how the white background and gradient opacity is changing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a9cuxr0d2bgg2vkbqubg.gif)

Now that we have a better understanding of when to animate the white background, let's create a table that we'll later translate to CSS.

This table only deals with the first heading but the same logic applies to the second and third heading as well.

Now that we're animating the white background as well, we'll need to split the animation into 12 steps (instead of 6).

|Part|Gradient opacity|White background opacity|
|--- |--- |--- |
|1|1|0|
|2|1 (start to fade out)|0 (start to fade in)|
|3|0.5 (still fading out)|1 (already faded in)|
|4|0 (faded out)|1|
|5|0|1|
|6|0|1|
|7|0|1|
|8|0|1|
|9|0|1|
|10|0 (start fading in)|1|
|11|0.5 (halfway faded in)|1 (start fading out)|
|12|1 (faded in)|0 (faded out)|


<br />

I know that this table seems to be a bit overwhelming and a bit tedious to put together

You can (and I highly encourage you to) create analogical tables for animating the second and third headings.

Anyways, we have done a lot of hard work, the last bit remaining is to express this table in CSS.

Let's start with the first heading. 👇🏻

```css
@keyframes first_heading_white_text {
  0%,
  /* 16.667 = 2/12 */
  16.667% {
    opacity: 0;
  }

  /* 25% = 3/12 */
  25%,
  /* 16.667 = 9/12 */
  91.667% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

.heading-line-first::before {
  /* ... */
  animation: first_heading_white_text 8s infinite;
}
```

Analogically, we want to animate the second and third headings' white backgrounds. 👇🏻

```css
@keyframes second_heading_white_text {
  0%,
  25% {
    opacity: 1;
  }

  33.333%,
  50% {
    opacity: 0;
  }

  58.333%,
  100% {
    opacity: 1;
  }
}

@keyframes third_heading_white_text {
  0%,
  58.333% {
    opacity: 1;
  }

  66.667%,
  83.333% {
    opacity: 0;
  }

  91.667%,
  100% {
    opacity: 1;
  }
}

.heading-line-second::before {
  /* ... */
  animation: second_heading_white_text 8s infinite;
}

.heading-line-third::before {
  /* ... */
  animation: third_heading_white_text 8s infinite;
}
```

🎉🥳 We are DONE!!! 🥳🎉

![animated heading saying animated. gradient. text.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbqxb7hkxp9sw4uv92e2.gif)

## Source code

You can find the source code for this tutorial on [CodeSandbox](https://codesandbox.io/s/animated-gradient-text-8kefo6).

If you liked this tutorial, you can also check out and subscribe to my [YouTube channel](https://www.youtube.com/channel/UCE7h4of6ywpAG87KXHV6UrQ) and [my blog](https://tomdohnal.com/)!