1. Web Design
  2. HTML/CSS
  3. SVG

How to Create 12 Different CSS Hover Effects From Scratch

Scroll to top

In this updated tutorial, we’re going to create ten different CSS hover effects and two additional ones that will require a little JavaScript.

We’ll learn loads along the way, including how to animate font icons, borders, arrows, SVG paths, and work with Lottie animations. You’ll be able to apply these hover effects to all kinds of situations. Let’s dive in!

Just to give you an idea of what you’ll learn during this tutorial, check out one of the demos we’ll be building:

1. Revealing Icon CSS Hover Effect

In this first example, we’ll explore the demo you’ve already seen: a hover effect where text is replaced by an icon with slide animation.

The animation in action

Start with the Page Markup

We’ll start with a plain unordered list which will represent a typical page menu. Each menu link will include two span elements. The first span will contain the anchor text, while the second one will hold a Font Awesome icon:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      <span>About</span>
5
      <span>
6
        <i class="fas fa-address-card" aria-hidden="true"></i>
7
      </span>
8
    </a>
9
  </li>
10
  <li>
11
    <a href="#0">
12
      <span>Projects</span>
13
      <span>
14
        <i class="fas fa-tasks" aria-hidden="true"></i>    
15
      </span>
16
    </a>
17
  </li>
18
  ...
19
</ul>

Specify the Styles

The list will be a flex container with horizontally centered content:

1
.menu {
2
  display: flex;
3
  justify-content: center;
4
}
We'll add this basis style to almost all the demos, so we won’t discuss it again. The demo also has some aesthetic styles we’ll reuse every time (such as the dark background etc.) that you can copy from the CodePen demo.

The first span in each item will have some padding around it:

1
.menu a span:first-child {
2
  display: inline-block;
3
  padding: 10px;
4
}

Then the second span will be absolutely positioned and hidden by default. Plus, its icon will be horizontally and vertically centered:

1
.menu a {
2
  display: block;
3
  position: relative;
4
  overflow: hidden;
5
}
6
7
.menu a span:last-child {
8
  position: absolute;
9
  top: 0;
10
  right: 0;
11
  bottom: 0;
12
  left: 0;
13
  display: flex;
14
  align-items: center;
15
  justify-content: center;
16
  transform: translateY(-100%);
17
}

Each time we hover over a link, its text will disappear. On the other hand, at that point, the associated icon will become visible:

1
.menu a span {
2
  transition: transform 0.2s ease-out;
3
}
4
5
.menu a:hover span:first-child {
6
  transform: translateY(100%);
7
}
8
9
.menu a:hover span:last-child {
10
  transform: none;
11
}

Happily enough, there’s the option to modify the direction of the slide animations according to our needs. By default during the hover animation, the icon will appear from top to bottom, while the text will be moved to the bottom. To change that behavior, we have to pass the data-animation attribute to the target list. Possible values are to-top, to-right, and to-left.

The corresponding styles are shown below:

1
.menu[data-animation="to-top"] a span:last-child {
2
  transform: translateY(100%);
3
}
4
5
.menu[data-animation="to-top"] a:hover span:first-child {
6
  transform: translateY(-100%);
7
}
8
9
.menu[data-animation="to-right"] a span:last-child {
10
  transform: translateX(-100%);
11
}
12
13
.menu[data-animation="to-right"] a:hover span:first-child {
14
  transform: translateX(100%);
15
}
16
17
.menu[data-animation="to-left"] a span:last-child {
18
  transform: translateX(100%);
19
}
20
21
.menu[data-animation="to-left"] a:hover span:first-child {
22
  transform: translateX(-100%);
23
}

Here’s the full CodePen demo that summarizes the aforementioned cases:

2. Revealing Swipe CSS Hover Effect

In this second example, we’ll continue with another hover effect where text is replaced by a Font Awesome icon. But, this time, the replacement will happen in the background and won’t be visible to us, thanks to a “swipe” that happens in the foreground.

The animation in action

Specify the Page Markup

As ever, we’ll start with an unordered list that will contain the page links. In this case, though, each of our links will have a data-icon attribute. The value of this attribute will match the Unicode representation of a Font Awesome icon:

1
<ul class="menu">
2
  <li>
3
    <a href="#0" data-icon="&#xf2bb;">
4
      About
5
    </a>
6
  </li>
7
  <li>
8
    <a href="#0" data-icon="&#xf0ae;">
9
      Projects
10
    </a>
11
  </li>
12
  ...
13
</ul>

Specify the Styles

We’ll then define the ::before and ::after pseudo-elements of all links. 

  • The ::before pseudo-element will be positioned in the center of the link, though it will initially be invisible. 
  • The ::after pseudo-element will cover the full link dimensions, but it will also be hidden by default. Its content will hold a Font Awesome icon stored in the relevant data-icon attribute:
1
:root {
2
  ...
3
  --text-color: #aaaebc;
4
  --timing-function: cubic-bezier(0.82, 0.2, 0.42, 1);
5
}
6
7
.menu a {
8
  position: relative;
9
  overflow: hidden;
10
}
11
12
.menu a::before,
13
.menu a::after {
14
  position: absolute;
15
  left: 0;
16
  width: 100%;
17
}
18
19
.menu a::before {
20
  content: "";
21
  top: 50%;
22
  transform: translate(-101%, -50%);
23
  height: 50%;
24
  z-index: 1;
25
  background: var(--text-color);
26
}
27
28
.menu a::after {
29
  content: attr(data-icon);
30
  font-family: "Font Awesome 5 Free";
31
  font-weight: 900;
32
  top: 0;
33
  display: flex;
34
  align-items: center;
35
  justify-content: center;
36
  height: 100%;
37
  color: var(--text-color);
38
  opacity: 0;
39
}

Each time we hover over a link, first the ::before pseudo-element will quickly appear. Then, it will be moved to the right and the icon will appear. During that time, the anchor text color will set to transparent. 

To achieve this precise timing synchronization, we have to customize the transitions' speed. For instance, we’ll add delays to the links as well as their ::after pseudo-element:

1
/*CUSTOM VARIABLES HERE*/
2
3
.menu a {
4
  transition: color 0s 0.25s var(--timing-function);
5
}
6
7
.menu a::before {
8
  transition: transform 0.5s var(--timing-function);
9
}
10
11
.menu a::after {
12
  transition: opacity 0s 0.25s var(--timing-function);
13
}
14
15
.menu a:hover {
16
  color: transparent;
17
}
18
19
.menu a:hover::before {
20
  transform: translate(101%, -50%);
21
}
22
23
.menu a:hover::after {
24
  opacity: 1;
25
}

If you prefer a reversed animation, just add the data-animation="to-left" attribute to the corresponding list.

The animation in action

Here’s the CodePen demo that covers both cases:

3. Animated Underline CSS Hover Effect

In our third example, we’ll look at a common hover effect where an underline is revealed with slide animation.

The animation in action

Specify the Page Markup

We’ll start with an unordered list that will store the page links:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      About
5
    </a>
6
  </li>
7
  <li>
8
    <a href="#0">
9
      Projects
10
    </a>
11
  </li>
12
  ...
13
</ul>

Specify the Styles

We’ll then define the ::before pseudo-element for all the links. We’ll place it to the bottom of its container and give it translate: scaleX(0), so it will initially be hidden. 

In addition, we’ll apply transform-origin: left to it because we want the animation to start from left to right:

1
.menu a {
2
  position: relative;
3
}
4
5
.menu a::before {
6
  content: "";
7
  position: absolute;
8
  bottom: 0;
9
  left: 0;
10
  width: 100%;
11
  height: 2px;
12
  background: linear-gradient(to right, #b47dcd, #e878a2, #eb85ab);
13
  z-index: 1;
14
  transform: scaleX(0);
15
  transform-origin: left;
16
  transition: transform 0.5s ease-in-out;
17
}

Each time we hover over a link, its pseudo-element will appear:

1
.menu a:hover::before {
2
  transform: scaleX(1);
3
}

Similar to the previous example, we have the option to change the direction of the slide animation. The line will appear from left to right by default, but to change that behavior, we have to pass the data-animation attribute to the target list. Possible values are to-left and center.

The corresponding styles are as follows:

1
.menu[data-animation="to-left"] a::before {
2
  transform-origin: right;
3
}
4
5
.menu[data-animation="center"] a::before {
6
  transform-origin: center;
7
}

Bonus!

We can go things one step further and change the transform-origin property value of the pseudo-element depending on its state. This makes the underline appear from one side, and disappear from the other.

The animation in action

Here are the rules that implement this functionality:

1
.menu[data-animation="bonus"] a::before {
2
  transform-origin: right;
3
  transition: transform 0.5s ease-in-out;
4
}
5
6
.menu[data-animation="bonus"] a:hover::before {
7
  transform-origin: left;
8
  transform: scaleX(1);
9
  transition-timing-function: cubic-bezier(0.2, 1, 0.82, 0.94);
10
}

Here’s the CodePen demo for the scenarios we discussed:

4. Multiline Animation CSS Hover Effect

Moving on, in this fourth example, we’ll cover a hover effect where multiple lines will be revealed sequentially. The beauty of this technique is that it will give us the impression that the border of the hovered element is being painted. How cool is that!

Note: this effect can also be achieved with SVG, and we’ll look at something similar in a later demo.

The animation in action

Specify the Page Markup

Again, our markup will consist of an unordered list. Each of the menu links will contain four empty span elements:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      About
5
      <span class="border border-top"></span>
6
      <span class="border border-right"></span>
7
      <span class="border border-bottom"></span>
8
      <span class="border border-left"></span>
9
    </a>
10
  </li>
11
  ...
12
</ul>

Specify the Styles

The span elements will be absolutely positioned and hidden by default. However, their position and transform-origin will be different:

1
.menu .border {
2
  position: absolute;
3
  left: 0;
4
  background: currentColor;
5
}
6
7
.menu .border-top,
8
.menu .border-bottom {
9
  width: 100%;
10
  height: 1px;
11
  transform: scaleX(0);
12
}
13
14
.menu .border-left,
15
.menu .border-right {
16
  width: 1px;
17
  height: 100%;
18
  transform: scaleY(0);
19
}
20
21
.menu .border-top,
22
.menu .border-left,
23
.menu .border-right {
24
  top: 0;
25
}
26
27
.menu .border-bottom {
28
  bottom: 0;
29
  transform-origin: bottom right;
30
}
31
32
.menu .border-top {
33
  transform-origin: top left;
34
}
35
36
.menu .border-left {
37
  transform-origin: bottom left;
38
}
39
40
.menu .border-right {
41
  left: auto;
42
  right: 0;
43
  transform-origin: top right;
44
}

As we hover over a link, its spans will become visible sequentially, following a clockwise rotation. To do so, we’ll control when the second, third, and fourth spans become visible:

1
:root {
2
  ...
3
  --transition-duration: 0.2s;
4
  --transition-delay: 0.2s;
5
}
6
7
.menu .border {
8
  transition: transform var(--transition-duration) ease-in-out;
9
}
10
11
.menu a:hover .border-top,
12
.menu a:hover .border-bottom {
13
  transform: scaleX(1);
14
}
15
16
.menu a:hover .border-left,
17
.menu a:hover .border-right {
18
  transform: scaleY(1);
19
}
20
21
.menu a:hover .border-right {
22
  transition-delay: var(--transition-delay);
23
}
24
25
.menu a:hover .border-bottom {
26
  transition-delay: calc(var(--transition-delay) * 2);
27
}
28
29
.menu a:hover .border-left {
30
  transition-delay: calc(var(--transition-delay) * 3);
31
}

By modifying the transform-origin property value and canceling the delays of our elements, we can easily generate another nice effect.

The animation in action

To incorporate this effect in our menu, we have to append the data-animation="diagonal" attribute to it. This will result in the following CSS styles:

1
.menu[data-animation="diagonal"] .border-left {
2
  transform-origin: top left;
3
}
4
5
.menu[data-animation="diagonal"] .border-right,
6
.menu[data-animation="diagonal"] .border-bottom {
7
  transform-origin: bottom right;
8
}
9
10
.menu[data-animation="diagonal"] a:hover [class^=border]{
11
  transition-delay: 0s;
12
}

Take a look at the CodePen demo that gives us those effects:

5. Animated Circle SVG Hover Effect

In this fifth example, we’ll walk through a hover effect where an SVG path is animated, encircling our link. The technique we’re going to use isn’t something new; in actual fact, Jake Archibald wrote about it some years ago.

The animation in action

Specify the Page Markup

Once more, we’ll start with an unordered list, but this time we’ll include an SVG nested inside each link:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      About
5
      <svg preserveAspectRatio="none" viewBox="0 0 546.714 178.143"><path d="M546.214 89.072c0 48.917-122.162 88.571-272.857 88.571C122.662 177.643.5 137.988.5 89.072.5 40.155 122.662.5 273.357.5c150.695 0 272.857 39.655 272.857 88.572z"/></svg>
6
    </a>
7
  </li>
8
  ...
9
</ul>

The SVG will be an ellipse and represented via the path element. We’ll give it preserveAspectRatio="none" because, as we’ll see in a moment, it should cover the full parent dimensions, even if it’s stretched.

Specify the Styles

The SVG will be absolutely positioned and centered within its container. Plus, as mentioned above, it will be big enough to cover the anchor text:

1
.menu a {
2
  position: relative;
3
}
4
5
.menu a svg {
6
  position: absolute;
7
  top: 50%;
8
  left: 50%;
9
  transform: translate(-50%, -50%);
10
  width: 120%;
11
  height: 120%;
12
  z-index: -1;
13
}

The path element will have a stroke and its fill color will be transparent:

1
.menu a svg path {
2
  fill: transparent;
3
  stroke-width: 5px;
4
  stroke: currentColor;
5
}

To animate the path, we’ll need two additional properties. Specifically, the stroke-dasharray and stroke-dashoffset properties. 

The stroke-dasharray property is responsible for creating dashes/gaps in our stroke. For example, by setting stroke-dasharray: 50 to the path, it will look as follows:

The svg with dashes around it

The stroke-dashoffset property is responsible for changing the position (offsetting) of those dashes.

So here’s the trick:

  • First, the value of the stroke-dasharray property should match the length of the path. Instead of guessing it, JavaScript provides a handy way for grabbing it through the getTotalLength() method. 
  • Next, we’ll also assign the resulting value (or its negative one for a reversed effect) to the stroke-dashoffset property. This neat combination will hide the SVG. 
  • Then, each time we hover over a link, we’ll run an animation that will move the stroke-dashoffset property value to 0. As a result of that, the path will be gradually “drawn”.

With all the above in mind, let’s first retrieve the path’s length (just for the first path) via JavaScript:

1
document.querySelector("li:nth-child(1) path").getTotalLength();
2
//1210.709716796875;

3
// you might see slightly different values depending on your browser

Then, we’ll assign this value to the corresponding properties:

1
.menu a svg path {
2
  stroke-dasharray: 1210.709716796875;
3
  stroke-dashoffset: -1210.709716796875; /*or the positive one*/
4
  transition: stroke-dashoffset .5s cubic-bezier(.29,.68,.74,1.02);
5
}
6
7
.menu a:hover svg path {
8
  stroke-dashoffset: 0;
9
}

Lastly, as an improvement, instead of hard coding the stroke-dasharray and stroke-dashoffset values, we could have specified them dynamically through JavaScript (see if you can do that bit yourself!).

Here’s the final demo, which gives us a cool chalkboard hover effect:

6. SVG Wavy UnderLine Hover Effect

Now that we know the basic principles behind the SVG path animation, let’s have a quick look at two other very similar examples. In both examples, there won’t be any need to stretch the SVGs, so we won’t make use of the preserveAspectRatio="none" attribute.

The first one will reveal a wavy underline.

The animation in action

Here’s the demo:

7. Pointing SVG Arrow Hover Effect

The third SVG demo will reveal an Iconmonstr icon (a pointing arrow). 

The animation in action

Here’s the demo for you to reverse engineer:

8. Lottie Animation Hover Effect

In this eighth example, we'll cover a hover effect where a Lottie animation is played infinitely. If you are unfamiliar with this concept, take some time to read this tutorial before continuing.

For this demo that will require some JavaScript, we'll use a free Lottie animation taken from the LottieFiles library and work with the LottieFiles web player. If you haven't used this player before, don't worry. There's also a dedicated tutorial that describes all the steps needed to incorporate it into a project. Be sure to read it first, then come back.

The animation in action

Specify the Page Markup

We’ll start with the usual unordered list. Each of the menu links will contain the same lottie-player custom element:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      About
5
      <lottie-player src="https://assets8.lottiefiles.com/datafiles/B1zOc97lUJINcA2/data.json" style="width: 140px; height: 140px;" loop></lottie-player>
6
    </a>
7
  </li>
8
  <li>
9
    <a href="#0">
10
      Projects
11
      <lottie-player src="https://assets8.lottiefiles.com/datafiles/B1zOc97lUJINcA2/data.json" style="width: 140px; height: 140px;" loop></lottie-player>
12
    </a>
13
  </li>
14
  <li>
15
    <a href="#0">
16
      Clients
17
      <lottie-player src="https://assets8.lottiefiles.com/datafiles/B1zOc97lUJINcA2/data.json" style="width: 140px; height: 140px;" loop></lottie-player>
18
    </a>
19
  </li>
20
  <li>
21
    <a href="#0">
22
      Contact
23
      <lottie-player src="https://assets8.lottiefiles.com/datafiles/B1zOc97lUJINcA2/data.json" style="width: 140px; height: 140px;" loop></lottie-player>
24
    </a>
25
  </li>
26
</ul>

Notice that we give this element fixed dimensions (140px x 140px). Feel free to make it bigger or smaller depending on your needs.

Specify the Styles

The lottie-player will be absolutely positioned and centered within its container. Plus, it will be hidden by default:

1
.menu a {
2
  position: relative;
3
}
4
5
.menu a lottie-player {
6
  position: absolute;
7
  top: 50%;
8
  left: 50%;
9
  transform: translate(-50%, -50%);
10
  visibility: hidden;
11
}
12
13
.menu a lottie-player.visible {
14
  visibility: visible;
15
}

Add the JavaScript

Each time we hover over a link, its associated animation will appear thanks to the visible class and play:

1
const links = document.querySelectorAll(".menu a");
2
const visibleClass = "visible";
3
4
links.forEach((link) => {
5
  link.addEventListener("mouseenter", function () {
6
    const player = this.querySelector("lottie-player");
7
    player.classList.add(visibleClass);
8
    player.play();
9
  });
10
11
  link.addEventListener("mouseleave", function () {
12
    const player = this.querySelector("lottie-player");
13
    player.classList.remove(visibleClass);
14
    player.stop();
15
  });
16
});

Here’s the related demo:

9. Lottie Animation With Random Color Hover Effect

In this ninth example, we'll continue our exploration in the Lottie animations world. This time though, some things will change during the hover effect. That said, the animation will appear yet also receive a random color coming from an array of colors.

Again, for this demo that will require some JavaScript, we'll use a free Lottie animation taken from the LottieFiles library and work with the LottieFiles web player.

The animation in action

Specify the Page Markup

Once again, we'll start with the usual unordered list and the same lottie-player custom element inside each link:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      About
5
      <lottie-player preserveAspectRatio="none" src="https://assets4.lottiefiles.com/packages/lf20_1wDQvS.json" loop></lottie-player>
6
    </a>
7
  </li>
8
  <li>
9
    <a href="#0">
10
      Projects
11
      <lottie-player preserveAspectRatio="none" src="https://assets4.lottiefiles.com/packages/lf20_1wDQvS.json" loop></lottie-player>
12
    </a>
13
  </li>
14
  <li>
15
    <a href="#0">
16
      Clients
17
      <lottie-player preserveAspectRatio="none" src="https://assets4.lottiefiles.com/packages/lf20_1wDQvS.json" loop></lottie-player>
18
    </a>
19
  </li>
20
  <li>
21
    <a href="#0">
22
      Contact
23
      <lottie-player preserveAspectRatio="none" src="https://assets4.lottiefiles.com/packages/lf20_1wDQvS.json" loop></lottie-player>
24
    </a>
25
  </li>
26
</ul>

However, this time our animation won't contain fixed dimensions. It should cover the full parent dimensions, even if it’s stretched. That's the reason why we give preserveAspectRatio="none" to the custom element. 

Specify the Styles

The lottie-player will be absolutely positioned and hidden by default:

1
.menu a {
2
  position: relative;
3
}
4
5
.menu a lottie-player {
6
  position: absolute;
7
  top: 0;
8
  left: 0;
9
  width: 100%;
10
  height: 100%;
11
  visibility: hidden;
12
}
13
14
.menu a lottie-player.visible {
15
  visibility: visible;
16
}

Add the JavaScript

Each time we hover over a link, its associated animation will appear thanks to the visible class and play. Additionally, the animation will receive a random color selection from the arrayOfColors array:

1
const links = document.querySelectorAll(".menu a");
2
const visibleClass = "visible";
3
const arrayOfColors = [
4
  "chartreuse",
5
  "gold",
6
  "forestgreen",
7
  "crimson",
8
  "tomato",
9
  "wheat",
10
  "pink",
11
  "yellow",
12
  "ivory",
13
  "bisque",
14
  "snow",
15
  "lightcyan",
16
  "lightyellow",
17
  "linen",
18
  "mintcream",
19
  "pink"
20
];
21
22
function getRandomColor() {
23
  const arrayLength = arrayOfColors.length;
24
  const randomValue = Math.random() * arrayLength;
25
  const roundedNumber = Math.floor(randomValue);
26
  const color = arrayOfColors[roundedNumber];
27
  return color;
28
}
29
30
links.forEach((link) => {
31
  link.addEventListener("mouseenter", function (e) {
32
    const player = this.querySelector("lottie-player");
33
    player.shadowRoot.querySelector("path").style.fill = getRandomColor();
34
    player.classList.add(visibleClass);
35
    player.play();
36
  });
37
38
  link.addEventListener("mouseleave", function () {
39
    const player = this.querySelector("lottie-player");
40
    player.classList.remove(visibleClass);
41
    player.stop();
42
  });
43
});
Remember that the lottie-player is a custom element. To change its color, we have to modify the fill value of its path element. But, we cannot directly target this element by using JavaScript's standard traversing methods. To gain this ability, we have first to target the player's contents by using the ShadowRoot interface and more specifically its shadowRoot property.

Here’s the related demo:

10. Revealing Text CSS Hover Effect

In this tenth example, we’ll return our attention to the icon and text animations—something that we have already covered in the first two demos.

Specify the Page Markup

Again, the markup will be straightforward; just a list where each link will contain some text and a Font Awesome icon:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      <span>About</span>
5
      <i class="fas fa-address-card" aria-hidden="true"></i>
6
    </a>
7
  </li>
8
 ...
9
</ul>

Specify the Styles

By default, only the icons will appear. Each time we hover over a link, its text will appear smoothly from the top while its icon will move slightly to the bottom. It's worth mentioning that we'll use CSS Grid to position the text and icon instead of the traditional positioning approaches.

Here's a part of the styles:

1
.menu {
2
  display: inline-flex;
3
  padding: 0 20px;
4
  background: var(--white);
5
  box-shadow: 0 12px 8px rgb(0 0 0 / 8%);
6
  overflow: hidden;
7
}
8
9
.menu li:not(:last-child) {
10
  margin-right: 70px;
11
}
12
13
.menu a {
14
  position: relative;
15
  display: grid;
16
  padding: 40px 20px;
17
  font-size: 14px;
18
  font-weight: bold;
19
  text-transform: uppercase;
20
}
21
22
.menu a > * {
23
  grid-column: 1;
24
  grid-row: 1;
25
  transition: all 0.3s ease-out;
26
}
27
28
.menu a span {
29
  transform: translateY(-60px);
30
}
31
32
.menu a:hover span {
33
  color: black;
34
  transform: translateY(-20px);
35
}
36
37
.menu a:hover i {
38
  color: var(--black);
39
  transform: translateY(10px);
40
}

The resulting demo:

11. Animating Letters CSS Hover Effect

In this eleventh example, we'll split the text into characters and animate them sequentially. We'll base the implementation on a previous tutorial where we produced a "twisting text effect".

Specify the Page Markup

For this case, we'll wrap each character inside a span element and give it an incremented number that will denote its position inside its parent:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      <span class="char" style="--index: 1">A</span>
5
      <span class="char" style="--index: 2">b</span>
6
      <span class="char" style="--index: 3">o</span>
7
      <span class="char" style="--index: 4">u</span>
8
      <span class="char" style="--index: 5">t</span>
9
    </a>
10
  </li>
11
  ...
12
</ul>

Of course, here we manually split the text so you can understand how the effect will work. But, in a more realistic scenario, JavaScript will probably do this stuff for us.

Specify the Styles

Each time we hover over a link, we'll flip sequentially its characters along the y-axis. The more the index value, the bigger delay for the associated element to flip. 

Here are the important styles:

1
.menu a {
2
  position: relative;
3
  display: flex;
4
  padding: 10px;
5
  text-transform: uppercase;
6
}
7
8
.menu .char {
9
  transition: transform 0.3s ease-in-out;
10
}
11
12
.menu a:hover .char {
13
  transform: scaleY(-1);
14
  transition-delay: calc(0.05s * var(--index));
15
}

Nothing stops us from combining this effect with another one, like the animated underline of demo 3.

An alternative implementation might be to get away from the index value, and use a CSS preprocessor like SASS to generate the delays, by using a logic like this:

1
@for $i from 1 to 8 {
2
  .menu a:hover .char:nth-child(#{$i}) { 
3
    transition-delay: $i * 0.05s;
4
  }
5
}

Here’s the demo:

12. Animating Arrows CSS Hover Effect

In this last example, we’ll reveal an arrow by animating its background position.

Specify the Page Markup

We'll wrap each text link inside a span element like this:

1
<ul class="menu">
2
  <li>
3
    <a href="#0">
4
      <span>About</span>
5
    </a>
6
  </li>
7
  ...
8
</ul>

Specify the Styles

The ::before pseudo-element of each link will display the arrow. Here are the things to consider:

  • By default, it will be absolutely positioned and hidden.
  • The original dimensions of the SVG arrow are 201px x 51px. In our case, we'll make the arrow smaller yet preserve the aspect ratio. Also, we'll give it background-size: contain to make sure that it will always appear without being cut off.
  • To hide the arrow, we'll give it a negative background-position-x. Anything equal to or higher than -80px will work.

The starting styles:

1
.menu {
2
  padding: 0;
3
}
4
5
.menu li + li {
6
  margin-top: 20px;
7
}
8
9
.menu a {
10
  position: relative;
11
  display: inline-flex;
12
  text-transform: uppercase;
13
}
14
15
.menu a::before {
16
  content: "";
17
  position: absolute;
18
  left: 0;
19
  top: 50%;
20
  transform: translateY(-50%);
21
  width: 180px;
22
  height: 20px;
23
  background: url(long-arrow.svg) no-repeat -85px 0 / contain;
24
  transition: background-position 0.3s ease-in-out;
25
}
26
27
.menu a span {
28
  transition: transform 0.3s ease-in-out;
29
}

As we hover over a link, we'll reset the background position of its ::before pseudo-element back to 0 and move its span 85px to the right—a little more than the arrow's width so they aren't stuck:

1
.menu a:hover span {
2
  transform: translateX(85px);
3
}
4
5
.menu a:hover::before {
6
  background-position: 0;
7
}

The final demo:

Conclusion

That concludes another tutorial, folks! It was quite a long journey, but I hope that you enjoyed it and have learned some new things which you'll include in your front-end toolkit.

Last but not least, I have added all the demos of this tutorial to a CodePen Collection. Be sure to check it out and give it some love!

I’d love to see your favorite CSS hover effects. They can come from CodePen, an inspirational website, or somewhere else. Don’t forget to share them with us!

As always, thanks a lot for reading!

More CSS and JS Hover Effect Tutorials

Learn how to create even more interesting CSS hover effects with these Tuts+ tutorials:

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.