Recreating Dia's H1
The landing page for Dia is really well done. The H1 text has a colorful animation which is not the best part, but it can be outlined easily in a blog post.
The first word changes continuously through a set of words like Chat, Write, Plan and Learn.
The current words fades out and the next word fades in from left to right. We first see blue, then yellow, red, pink and finally black which stays visible until the next word change.
Intuition
The way to think about this is sliding a ribbon of colors behind a stencil cutout of the text. By moving the ribbon left or right, we can change which colors are visible through the text.
The character outlines are implemented using -webkit-text-stroke. This is supported in all major browsers1 but it is an unofficial property.
With the above demo we can see there are two major parts to the component.
- The ribbon of colors that slides left to right.
- A method to position and move the ribbon behind the text.
Creating the Ribbon
We'll use five colors for our ribbon.
The ribbon can be created using the linear-gradient CSS function. The linear-gradient() CSS function creates an image consisting of a progressive transition between two or more colors along a straight line2. The function can be assigned to the background-image property.
For our purposes, its very important we think of the ribbon as an image. This means we can use properties like background-size and background-position to manipulate it.
The to right value means the gradient will go from left to right (90 degrees). The colors will transition evenly across the width of the element.
Color stops
Instead of having the colors transition evenly, we can define color stops. Color stops define where each color starts and ends3.
These color stops are defined as percentages of the total width of the background image. The above values are equivalent to not defining any color stops at all since the colors are evenly spaced.
This will be useful when we want to set some part of the ribbon to transparent to allow for an invisible starting point for the animation.
Background Size & Position

What if we only wanted to show one of the cubes without editing the image? We can do this by manipulating the background-size and background-position properties.
The background-position CSS property sets the initial position for each background image. The position is relative to the position layer set by background-origin4.
Its hard to give a better explanation of these properties than ones already given at MDN or at CSS Tricks.
The key takeaway is that by changing the background-position we can slide the ribbon of colors left or right behind the text.
background-size allows us to zoom into parts of the ribbon not because we need to, but because we want to hide parts of the ribbon from view.
Instead of three cubes we will have the intial transparent part, a moving colorful part and a final black part.
Transparent. Why?
We need a transparent part so that when the component first loads, we don't see any colors. The colorful part should only be visible when it slides in from the left.
Without the transparent part, the colorful part will be visible immediately on load. We would see the colors change but we would not get the effect of the colors sliding in from the left.
Putting it all together
So the ribbon will have three parts:
- A transparent part at the start
- The colorful part in the middle
- A final color part at the end
All three will be the same width.
Using Color stops we can define the ribbon as below:
Linear Gradient
The linear-gradient has a black part from 0% to 33.33%, then the colorful part from 33.33% to 66.67% and finally the transparent part from 66.67% to 100%.
The maroon text is only for visibility.
Background Size & Position
Now we only want one third of the ribbon to be visible at any time. So we zoom in by setting the background-size to 300% (3 times the width of the element).
Which part of the ribbon is visible is determined by the background-position. It defaults to 0%. So as we see above the first third of the ribbon is visible, which is the black part.
Intially we want to see the transparent part. Since the vertical position is not important, we can update background-position-x.
Setting background-position-x to 100% we see the transparent part.
This value of background-position-x corresponds directly with the linear-gradient color stops. If we set it to 50% we would see the colorful part.
Neither background-position nor linear-gradient color stops are affected by background-size. For our purposes background-size is just a zoom level.
Animation
Now comes the animation part. We can animate the background-position-x from 100% to 0% to slide the colorful part into view.
Now there is a lot of stuff happening here. The animation will start after a 100ms delay, it will last for 300ms and it will ease in and out. Finally the forwards fill mode means that after the animation ends, the final state background-position-x: 0% will be retained.
The ribbon-slide keyframes defines the animation itself. At the start (0%) the background-position-x is 100% (transparent part visible) and at the end (100%) it is 0% (black part visible).
animation-duration: 2000msThe Final Trick
Now the final thing which makes this work is to make the text act as a stencil. This is done using the background-clip: text property.
We also need to make the text fill color transparent so that the background is visible through the text.
animation-duration: 2000msCycling through words
Cycling through words can be done using JavaScript or a framework of your choice. The idea is to change the text content every few seconds and re-trigger the animation.
The main challenge is preventing layout shift when the words change. As seen below, changing the words causes the whole heading to shift.
Preventing Layout Shift
This can be solved by setting a fixed width for the heading based on the longest word. This way when the words change, the width remains constant and there is no layout shift.
Also we use text-align: right so that shorter words align to the right edge. This keeps the visual position of the words consistent.
We define the width in ch units which are based on the width of the 0 character in the current font. This makes it easier to estimate the width needed for the longest word.
display: flex is used to align the word and the rest of the heading text horizontally with a gap of 0.5ch between them.
The white-space: nowrap prevents the words from wrapping to the next line and overflow: hidden ensures that there is no layout shift the words is instead clipped if it exceeds the width.
For all other words except the longest one, the heading is not perfectly centered. But it does still look visually centered. So its a good tradeoff to prevent layout shift.
Re-triggering the Animation
Remember to reset the background-position-x to 100% before re-triggering the animation so that the animation always starts from the transparent part.
Beyond that, the implementation details will depend on the framework you are using.
React Implementation
The animating word component can be implemented in React as below:
There are a couple of things that were added here to improve the experience.
A fade-out and fade-in effect is added when changing words. This is done by changing the opacity of the word wrapper with a transition. This makes the word change less abrupt. The duration is 300ms.
A key is added to the word span. This forces React to treat each word as a new element, ensuring the animation restarts correctly when the word changes. This way we don't have to manually reset the background-position-x. Other frameworks will have their own way of doing this.
The width was moved from the word to a wrapper element to prevent issues with the animation and text clipping.
The gap 0.5ch is arbitrarily chosen but intended to match the spacing between words in the rest of the heading.