Tag: Hover

Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D

We’ve walked through a series of posts now about interesting approaches to CSS hover effects. We started with a bunch of examples that use CSS background properties, then moved on to the text-shadow property where we technically didn’t use any shadows. We also combined them with CSS variables and calc() to optimize the code and make it easy to manage.

In this article, we will build off those two articles to create even more complex CSS hover animations. We’re talking about background clipping, CSS masks, and even getting our feet wet with 3D perspectives. In other words, we are going to explore advanced techniques this time around and push the limits of what CSS can do with hover effects!

Cool Hover Effects series:

  1. Cool Hover Effects That Use Background Properties
  2. Cool Hover Effects That Use CSS Text Shadow
  3. Cool Hover Effects That Use Background Clipping, Masks, and 3D (you are here!)

Here’s just a taste of what we’re making:

Hover effects using background-clip

Let’s talk about background-clip. This CSS property accepts a text keyword value that allows us to apply gradients to the text of an element instead of the actual background.

So, for example, we can change the color of the text on hover as we would using the color property, but this way we animate the color change:

All I did was add background-clip: text to the element and transition the background-position. Doesn’t have to be more complicated than that!

But we can do better if we combine multiple gradients with different background clipping values.

In that example, I use two different gradients and two values with background-clip. The first background gradient is clipped to the text (thanks to the text value) to set the color on hover, while the second background gradient creates the bottom underline (thanks to the padding-box value). Everything else is straight up copied from the work we did in the first article of this series.

How about a hover effect where the bar slides from top to bottom in a way that looks like the text is scanned, then colored in:

This time I changed the size of the first gradient to create the line. Then I slide it with the other gradient that update the text color to create the illusion! You can visualize what’s happening in this pen:

We’ve only scratched the surface of what we can do with our background-clipping powers! However, this technique is likely something you’d want to avoid using in production, as Firefox is known to have a lot of reported bugs related to background-clip. Safari has support issues as well. That leaves only Chrome with solid support for this stuff, so maybe have it open as we continue.

Let’s move on to another hover effect using background-clip:

You’re probably thinking this one looks super easy compared to what we’ve just covered — and you are right, there’s nothing fancy here. All I am doing is sliding one gradient while increasing the size of another one.

But we’re here to look at advanced hover effects, right? Let’s change it up a bit so the animation is different when the mouse cursor leaves the element. Same hover effect, but a different ending to the animation:

Cool right? let’s dissect the code:

.hover {   --c: #1095c1; /* the color */    color: #0000;   background:      linear-gradient(90deg, #fff 50%, var(--c) 0) calc(100% - var(--_p, 0%)) / 200%,      linear-gradient(var(--c) 0 0) 0% 100% / var(--_p, 0%) no-repeat,     var(--_c, #0000);   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   transition: 0s, color .5s, background-color .5s; } .hover:hover {   color: #fff;   --_c: var(--c);   --_p: 100%;   transition: 0.5s, color 0s .5s, background-color 0s .5s; }

We have three background layers — two gradients and the background-color defined using --_c variable which is initially set to transparent (#0000). On hover, we change the color to white and the --_c variable to the main color (--c).

Here’s what is happening on that transition: First, we apply a transition to everything but we delay the color and background-color by 0.5s to create the sliding effect. Right after that, we change the color and the background-color. You might notice no visual changes because the text is already white (thanks to the first gradient) and the background is already set to the main color (thanks to the second gradient).

Then, on mouse out, we apply an instant change to everything (notice the 0s delay), except for the color and background-color that have a transition. This means that we put all the gradients back to their initial states. Again, you will probably see no visual changes because the text color and background-color already changed on hover.

Lastly, we apply the fading to color and a background-color to create the mouse-out part of the animation. I know, it may be tricky to grasp but you can better visualize the trick by using different colors:

Hover the above a lot of times and you will see the properties that are animating on hover and the ones animating on mouse out. You can then understand how we reached two different animations for the same hover effect.

Let’s not forget the DRY switching technique we used in the previous articles of this series to help reduce the amount of code by using only one variable for the switch:

.hover {   --c: 16 149 193; /* the color using the RGB format */    color: rgb(255 255 255 / var(--_i, 0));   background:     /* Gradient #1 */     linear-gradient(90deg, #fff 50%, rgb(var(--c)) 0) calc(100% - var(--_i, 0) * 100%) / 200%,     /* Gradient #2 */     linear-gradient(rgb(var(--c)) 0 0) 0% 100% / calc(var(--_i, 0) * 100%) no-repeat,     /* Background Color */     rgb(var(--c)/ var(--_i, 0));   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   --_t: calc(var(--_i,0)*.5s);   transition:      var(--_t),     color calc(.5s - var(--_t)) var(--_t),     background-color calc(.5s - var(--_t)) var(--_t); } .hover:hover {   --_i: 1; }

If you’re wondering why I reached for the RGB syntax for the main color, it’s because I needed to play with the alpha transparency. I am also using the variable --_t to reduce a redundant calculation used in the transition property.

Before we move to the next part here are more examples of hover effects I did a while ago that rely on background-clip. It would be too long to detail each one but with what we have learned so far you can easily understand the code. It can be a good inspiration to try some of them alone without looking at the code.

I know, I know. These are crazy and uncommon hover effects and I realize they are too much in most situations. But this is how to practice and learn CSS. Remember, we pushing the limits of CSS hover effects. The hover effect may be a novelty, but we’re learning new techniques along the way that can most certainly be used for other things.

Hover effects using CSS mask

Guess what? The CSS mask property uses gradients the same way the background property does, so you will see that what we’re making next is pretty straightforward.

Let’s start by building a fancy underline.

I’m using background to create a zig-zag bottom border in that demo. If I wanted to apply an animation to that underline, it would be tedious to do it using background properties alone.

Enter CSS mask.

The code may look strange but the logic is still the same as we did with all the previous background animations. The mask is composed of two gradients. The first gradient is defined with an opaque color that covers the content area (thanks to the content-box value). That first gradient makes the text visible and hides the bottom zig-zag border. content-box is the mask-clip value which behaves the same as background-clip

linear-gradient(#000 0 0) content-box

The second gradient will cover the whole area (thanks to padding-box). This one has a width that’s defined using the --_p variable, and it will be placed on the left side of the element.

linear-gradient(#000 0 0) 0 / var(--_p, 0%) padding-box

Now, all we have to do is to change the value of --_p on hover to create a sliding effect for the second gradient and reveal the underline.

.hover:hover {   --_p: 100%;   color: var(--c); }

The following demo uses with the mask layers as backgrounds to better see the trick taking place. Imagine that the green and red parts are the visible parts of the element while everything else is transparent. That’s what the mask will do if we use the same gradients with it.

With such a trick, we can easily create a lot of variation by simply using a different gradient configuration with the mask property:

Each example in that demo uses a slightly different gradient configuration for the mask. Notice, too, the separation in the code between the background configuration and the mask configuration. They can be managed and maintained independently.

Let’s change the background configuration by replacing the zig-zag underline with a wavy underline instead:

Another collection of hover effects! I kept all the mask configurations and changed the background to create a different shape. Now, you can understand how I was able to reach 400 hover effects without pseudo-elements — and we can still have more!

Like, why not something like this:

Here’s a challenge for you: The border in that last demo is a gradient using the mask property to reveal it. Can you figure out the logic behind the animation? It may look complex at first glance, but it’s super similar to the logic we’ve looked at for most of the other hover effects that rely on gradients. Post your explanation in the comments!

Hover effects in 3D

You may think it’s impossible to create a 3D effect with a single element (and without resorting to pseudo-elements!) but CSS has a way to make it happen.

What you’re seeing there isn’t a real 3D effect, but rather a perfect illusion of 3D in the 2D space that combines the CSS background, clip-path, and transform properties.

Breakdown of the CSS hover effect in four stages.
The trick may look like we’re interacting with a 3D element, but we’re merely using 2D tactics to draw a 3D box

The first thing we do is to define our variables:

--c: #1095c1; /* color */ --b: .1em; /* border length */ --d: 20px; /* cube depth */

Then we create a transparent border with widths that use the above variables:

--_s: calc(var(--d) + var(--b)); color: var(--c); border: solid #0000; /* fourth value sets the color's alpha */ border-width: var(--b) var(--b) var(--_s) var(--_s);

The top and right sides of the element both need to equal the --b value while the bottom and left sides need to equal to the sum of --b and --d (which is the --_s variable).

For the second part of the trick, we need to define one gradient that covers all the border areas we previously defined. A conic-gradient will work for that:

background: conic-gradient(   at left var(--_s) bottom var(--_s),   #0000 90deg,var(--c) 0  )   0 100% / calc(100% - var(--b)) calc(100% - var(--b)) border-box;
Diagram of the sizing used for the hover effect.

We add another gradient for the third part of the trick. This one will use two semi-transparent white color values that overlap the first previous gradient to create different shades of the main color, giving us the illusion of shading and depth.

conic-gradient(   at left var(--d) bottom var(--d),   #0000 90deg,   rgb(255 255 255 / 0.3) 0 225deg,   rgb(255 255 255 / 0.6) 0 ) border-box
Showing the angles used to create the hover effect.

The last step is to apply a CSS clip-path to cut the corners for that long shadow sorta feel:

clip-path: polygon(   0% var(--d),    var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%,    0% 100% )
Showing the coordinate points of the three-dimensional cube used in the CSS hover effect.

That’s all! We just made a 3D rectangle with nothing but two gradients and a clip-path that we can easily adjust using CSS variables. Now, all we have to do is to animate it!

Notice the coordinates from the previous figure (indicated in red). Let’s update those to create the animation:

clip-path: polygon(   0% var(--d), /* reverses var(--d) 0% */   var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%, /* reverses 100% calc(100% - var(--d)) */    0% 100% /* reverses var(--d) calc(100% - var(--d)) */ )

The trick is to hide the bottom and left parts of the element so all that’s left is a rectangular element with no depth whatsoever.

This pen isolates the clip-path portion of the animation to see what it’s doing:

The final touch is to move the element in the opposite direction using translate — and the illusion is perfect! Here’s the effect using different custom property values for varying depths:

The second hover effect follows the same structure. All I did is to update a few values to create a top left movement instead of a top right one.

Combining effects!

The awesome thing about everything we’ve covered is that they all complement each other. Here is an example where I am adding the text-shadow effect from the second article in the series to the background animation technique from the first article while using the 3D trick we just covered:

The actual code might be confusing at first, but go ahead and dissect it a little further — you’ll notice that it’s merely a combination of those three different effects, pretty much smushed together.

Let me finish this article with a last hover effect where I am combining background, clip-path, and a dash of perspective to simulate another 3D effect:

I applied the same effect to images and the result was quite good for simulating 3D with a single element:

Want a closer look at how that last demo works? I wrote something up on it.

Wrapping up

Oof, we are done! I know, it’s a lot of tricky CSS but (1) we’re on the right website for that kind of thing, and (2) the goal is to push our understanding of different CSS properties to new levels by allowing them to interact with one another.

You may be asking what the next step is from here now that we’re closing out this little series of advanced CSS hover effects. I’d say the next step is to take all that we learned and apply them to other elements, like buttons, menu items, links, etc. We kept things rather simple as far as limiting our tricks to a heading element for that exact reason; the actual element doesn’t matter. Take the concepts and run with them to create, experiment with, and learn new things!


Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,

Cool Hover Effects That Use CSS Text Shadow

In my last article we saw how CSS background properties allow us to create cool hover effects. This time, we will focus on the CSS text-shadow property to explore even more interesting hovers. You are probably wondering how adding shadow to text can possibly give us a cool effect, but here’s the catch: we’re not actually going to make any shadows for these text hover effects.

text-shadow but no text shadows?

Let me clear the confusion by showing the hover effects we are going to build in the following demo:

Without looking at the code many of you will, intuitively, think that for each hover effect we are duplicating the text and then independently animating them. Now, if you check the code you will see that none of the text is actually duplicated in the HTML. And did you notice that there is no use of content: "text" in the CSS?

The text layers are completely made with text-shadow!

Hover effect #1

Let’s pick apart the CSS:

.hover-1 {   line-height: 1.2em;   color: #0000;   text-shadow:      0 0 #000,      0 1.2em #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 -1.2em #000,      0 0 #1095c1; }

The first thing to notice is that I am making the color of the actual text transparent (using #0000) in order to hide it. After that, I am using text-shadow to create two shadows where I am defining only two length values for each one. That means there’s no blur radius, making for a sharp, crisp shadow that effectively produces a copy of the text with the specified color.

That’s why I was able to claim in the introduction that there are no shadows in here. What we’re doing is less of a “classic” shadow than it is a simple way to duplicate the text.

Diagram of the start and end of the hover effect.

We have two text layers that we move on hover. If we hide the overflow, then the duplicated text is out of view and the movement makes it appear as though the actual text is being replaced by other text. This is the main trick that that makes all of the examples in this article work.

Let’s optimize our code. I am using the value 1.2em a lot to define the height and the offset of the shadows, making it an ideal candidate for a CSS custom property (which we’re calling --h):

.hover-1 {   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 0 #000,      0 var(--h) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 calc(-1 * var(--h)) #000,      0 0 #1095c1; }

We can still go further and apply more calc()-ulations to streamline things to where we only use the text-shadow once. (We did the same in the previous article.)

.hover-1 {   --h: 1.2em;       line-height: var(--h);   color: #0000;   text-shadow:      0 calc(-1*var(--_t, 0em)) #000,      0 calc(var(--h) - var(--_t, 0em)) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   --_t: var(--h); }

In case you are wondering why I am adding an underscore to the --_t variable, it’s just a naming convention I am using to distinguish between the variables we use to control the effect that the user can update (like --h) and the internal variables that are only used for optimization purposes that we don’t need to change (like --_t ). In other words, the underscore is part of the variable name and has no special meaning.

We can also update the code to get the opposite effect where the duplicated text slides in from the top instead:

All we did is a small update to the text-shadow property — we didn’t touch anything else!

Hover effect #2

For this one, we will animate two properties: text-shadow and background. Concerning the text-shadow, we still have two layers like the previous example, but this time we will move only one of them while making the color of the other one transparent during the swap.

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   transition: 0.3s; } .hover-2:hover {   --_t: 0;   --_c: #0000; }

On hover, we move the white text layer to the top while changing the color of the other one to transparent. To this, we add a background-size animation applied to a gradient:

And finally, we add overflow: hidden to keep the animation only visible inside the element’s boundaries:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t,var(--h)) #fff,     0 0 var(--_c, #000);   background:      linear-gradient(#1095c1 0 0)      bottom/100% var(--_d, 0) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_d: 100%;   --_t: 0;   --_c: #0000; }

What we’ve done here is combine the CSS text-shadow and background properties to create a cool hover effect. Plus, we were able to use CSS variables to optimize the code.

If the background syntax looks strange to you, I highly recommend reading my previous article. The next hover effect also relies on an animation I detailed in that article. Unless you are comfortable with CSS background trickery, I’d suggest reading that article before continuing this one for more context.

In the previous article, you show us how to use only one variable to create the hover effect — is it possible to do that here?

Yes, absolutely! We can indeed use that same DRY switching technique so that we’re only working with a single CSS custom property that merely switches values on hover:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_i, var(--h)) #fff,     0 0 rgb(0 0 0 / calc(var(--_i, 1) * 100%) );   background:      linear-gradient(#1095c1 0 0)      bottom/100% calc(100% - var(--_i, 1) * 100%) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_i: 0; }

Hover effect #3

This hover effect is nothing but a combination of two effects we’ve already made: the second hover effect of the previous article and the first hover effect in this article.

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    /* The first hover effect in this article */   line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--_t, 0em)) var(--c),      0 calc(var(--h) - var(--_t, 0em)) #fff;   /* The second hover effect from the previous article */   background:      linear-gradient(var(--c) 0 0) no-repeat      calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);   transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s)); } .hover-3:hover {   --_t: var(--h);   --_p: 100%;   --_s: .3s }

All I did was copy and paste the effects from those other examples and make minor adjustments to the variable names. They make for a neat hover effect when they’re combined! At first glance, such an effect may look complex and difficult but, in the end, it’s merely two relatively easy effects made into one.

Optimizing the code with the DRY switching variable technique should also be an easy task if we consider the previous optimizations we’ve already done:

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--h) * var(--_i, 0)) var(--c),      0 calc(var(--h) * (1 - var(--_i, 0))) #fff;   background:      linear-gradient(var(--c) 0 0) no-repeat     calc(200% - var(--_i, 0) * 100%) 100% / 200% calc(100% * var(--_i, 0) + .08em);   transition: .3s calc(var(--_i, 0) * .3s), background-position .3s calc(.3s - calc(var(--_i, 0) * .3s)); } .hover-3:hover {   --_i: 1; }

Hover effect #4

This hover effect is an improvement of the second one. First, let’s introduce a clip-path animation to reveal one of the text layers before it moves:

Here’s another illustration to better understand what is happening:

Diagram of the start and end of the text hover.

Initially, we use inset(0 0 0 0) which is similar to overflow: hidden in that all we see is the actual text. On hover, we update the the third value (which represent the bottom offset) using a negative value equal to the height to reveal the text layer placed at the bottom.

From there, we can add this to the second hover effect we made in this article, and this is what we get:

We are getting closer! Note that we need to first run the clip-path animation and then everything else. For this reason, we can add a delay to all of the properties on hover, except clip-path:

transition: 0.4s 0.4s, clip-path 0.4s;

And on mouse out, we do the opposite:

transition: 0.4s, clip-path 0.4s 0.4s;

The final touch is to add a box-shadow to create the sliding effect of the blue rectangle. Unfortunately, background is unable to produce the effect since backgrounds are clipped to the content area by default. Meanwhile, box-shadow can go outside the content area.

.hover-4 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;      line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   box-shadow: 0 var(--_t, var(--h)) var(--c);   clip-path: inset(0 0 0 0);   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat;   transition: 0.4s, clip-path 0.4s 0.4s; } .hover-4:hover {   --_t: 0;   --_c: #0000;   clip-path: inset(0 0 calc(-1 * var(--h)) 0);   transition: 0.4s 0.4s, clip-path 0.4s; }

If you look closely at the box-shadow, you will see it has the same values as the white text layer inside text-shadow. This is logical since both need to move the same way. Both will slide to the top. Then the box-shadow goes behind the element while text-shadow winds up on the top.

Here is a demo with some modified values to visualize how the layers move:

Wait, The background syntax is a bit different from the one used in the second hover effect!

Good catch! Yes, we are using a different technique with background that produces the same effect. Instead of animating the size from 0% to 100%, we are animating the position.

If we don’t specify a size on our gradient, then it take up the full width and height by default. Since we know the height of our element (--h) we can create a sliding effect by updating the position from 0 var(--h) to 0 0.

.hover-4 {   /* ... */   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat; } .hover-4:hover {   --_t: 0; }

We could have used the background-size animation to get the same effect, but we just added another trick to our list!

In the demos, you also used inset(0 0 1px 0)… why?

I sometimes add or remove a few pixels or percentages here and there to refine anything that looks off. In this case, a bad line was appearing at the bottom and adding 1px removed it.

What about the DRY switch variable optimization?

I am leaving this task for you! After those four hover effects and the previous article, you should be able to update the code so it only uses one variable. I’d love to see you attempt it in the comments!

Your turn!

Let me share one last hover effect which is another version of the previous one. Can you find out how it’s done without looking at the code? It’s a good exercise, so don’t cheat!

Wrapping up

We looked at a bunch of examples that show how one element and few lines of CSS are enough to create some pretty complex-looking hover effects on text elements — no pseudo elements needed! We were even able to combine techniques to achieve even more complex animations with a small amount of effort.

If you’re interested in going deeper than the four text-shadow hover effects in this article, check my collection of 500 hover effects where I am exploring all kinds of different techniques.


Cool Hover Effects That Use CSS Text Shadow originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Cool Hover Effects That Use Background Properties

A while ago, Geoff wrote an article about a cool hover effect. The effect relies on a combination of CSS pseudo-elements, transforms, and transitions. A lot of comments have shown that the same effect can be done using background properties. Geoff mentioned that was his initial thought and that’s what I was thinking as well. I am not saying the pseudo-element he landed on is bad, but knowing different methods to achieve the same effect can only be a good thing.

In this post, we will re-work that hover effect, but also expand it into other types of hover effects that only use CSS background properties.

You can see the background properties at work in that demo, as well as how we can use custom properties and the calc() function to do even more. We are going to learn how to combine all of these so we are left with nicely optimized code!

Hover effect #1

Let’s start with the first effect which is the reproduction of the one detailed by Geoff in his article. The code used to achieve that effect is the following:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0) / var(--p, 0) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   color: #fff; }

If we omit the color transition (which is optional), we only need three CSS declarations to achieve the effect. You are probably surprised how small the code is, but you will see how we got there.

First, let’s start with a simple background-size transition:

We are animating the size of a linear gradient from 0 100% to 100% 100%. That means the width is going from 0 to 100% while the background itself remains at full height. Nothing complex so far.

Let’s start our optimizations. We first transform our gradient to use the color only once:

background-image: linear-gradient(#1095c1 0 0);

The syntax might look a bit strange, but we are telling the browser that one color is applied to two color stops, and that’s enough to define a gradient in CSS. Both color stops are 0, so the browser automatically makes the last one 100% and fills our gradient with the same color. Shortcuts, FTW!

With background-size, we can omit the height because gradients are full height by default. We can do a transition from background-size: 0 to background-size: 100%.

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 0;   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   background-size: 100%; }

Let’s introduce a custom property to avoid the repetition of background-size:

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: var(--p, 0%);   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are not defining --p initially, so the fallback value (0% in our case) will be used. On hover, we define a value that replaces the fallback one ( 100%).

Now, let’s combine all the background properties using the shorthand version to get:

.hover-1 {   background: linear-gradient(#1095c1 0 0) left / var(--p, 0%) no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are getting closer! Note that I have introduced a left value (for the background-position) which is mandatory when defining the size in the background shorthand. Plus, we need it anyway to achieve our hover effect.

We need to also update the position on hover. We can do that in two steps:

  1. Increase the size from the right on mouse hover.
  2. Decrease the size from the left on mouse out.

To do this, we need to update the background-position on hover as well:

We added two things to our code:

  • A background-position value of right on hover
  • A transition-duration of 0s on the background-position

This means that, on hover, we instantly change the background-position from left (see, we needed that value!) to right so the background’s size will increase from the right side. Then, when the mouse cursor leaves the link, the transition plays in reverse, from right to left, making it appear that we are decreasing the background’s size from the left side. Our hover effect is done!

But you said we only needed three declarations and there are four.

That’s true, nice catch. The left and right values can be changed to 0 0 and 100% 0, respectively; and since our gradient is already full height by default, we can get by using 0 and 100%.

.hover-1 {   background: linear-gradient(#1095c1 0 0) 0 / var(--p, 0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   background-position: 100%; }

See how background-position and --p are using the same values? Now we can reduce the code down to three declarations:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0%) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

The custom property --p is defining both the background position and size. On hover, It will update both of them as well. This is a perfect use case showing how custom properties can help us reduce redundant code and avoid writing properties more than once. We define our setting using custom properties and we only update the latter on hover.

But the effect Geoff described is doing the opposite, starting from left and ending at right. How do we do that when it seems we cannot rely on the same variable?

We can still use one variable and update our code slightly to achieve the opposite effect. What we want is to go from 100% to 0% instead of 0% to 100%. We have a difference of 100% that we can express using calc(), like this:

.hover-1 {   background: linear-gradient(#1095c1 0 0) calc(100% - var(--p,0%)) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

--p will change from 0% to 100%, but the background’s position will change from 100% to 0%, thanks to calc().

We still have three declarations and one custom property, but a different effect.

Before we move to the next hover effect, I want to highlight something important that you have probably noticed. When dealing with custom properties, I am using 0% (with a unit) instead of a unit-less 0. The unit-less zero may work when the custom property is alone, but will fail inside calc() where we need to explicitly define the unit. I may need another article to explain this quirk but always remember to add the unit when dealing with custom properties. I have two answers on StackOverflow (here and here) that go into more detail.

Hover effect #2

We need a more complex transition for this effect. Let’s take a look at a step-by-step illustration to understand what is happening.

Diagram showing the hover effect in three pieces.
Initially, a fixed-height, full-width gradient is outside of view. Then we move the gradient to the right to cover the bottom side. Finally, we increase the size of the gradient from the fixed height to 100% to cover the whole element.

We first have a background-position transition followed by a background-size one. Let’s translate this into code:

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 100% .08em; /* .08em is our fixed height; modify as needed. */   background-position: /* ??? */;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 100% 100%;   background-position: /* ??? */; }

Note the use of two transition values. On hover, we need to first change the position and later the size, which is why we are adding a delay to the size. On mouse out, we do the opposite.

The question now is: what values do we use for background-position? We left those blank above. The background-size values are trivial, but the ones for background-position are not. And if we keep the actual configuration we’re unable to move our gradient.

Our gradient has a width equal to 100%, so we cannot use percentage values on background-position to move it.

Percentage values used with background-position are always a pain especially when you use them for the first time. Their behavior is non-intuitive but well defined and easy to understand if we get the logic behind it. I think it would take another article for a full explanation why it works this way, but here’s another “long” explanation I posted over at Stack Overflow. I recommend taking a few minutes to read that answer and you will thank me later!

The trick is to change the width to something different than 100%. Let’s use 200%. We’re not worried about the background exceeding the element because the overflow is hidden anyway.

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 200% .08em;   background-position: 200% 100%;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 200% 100%;   background-position: 100% 100%; }

And here’s what we get:

It’s time to optimize our code. If we take the ideas we learned from the first hover effect, we can use shorthand properties and write fewer declarations to make this work:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     var(--p, 200%) 100% / 200% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-2:hover {   --p: 100%;   --t: .3s; }

We add all the background properties together using the shorthand version then we use --p to express our values. The sizes change from .08em to 100% and the position from 200% to 100%

I am also using another variable --t , to optimize the transition property. On mouse hover we have it set to a .3s value, which gives us this:

transition: .3s .3s, background-position .3s 0s;

On mouse out, --t is undefined, so the fallback value will be used:

transition: .3s 0s, background-position .3s .3s;

Shouldn’t we have background-size in the transition?

That is indeed another optimization we can make. If we don’t specify any property it means “all” the properties, so the transition is defined for “all” the properties (including background-size and background-position). Then it’s defined again for background-position which is similar to defining it for background-size, then background-position.

“Similar” is different than saying something is the “same.” You will see a difference if you change more properties on hover, so the last optimization might be unsuitable in some cases.

Can we still optimize the code and use only one custom property?

Yes, we can! Ana Tudor shared a great article explaining how to create DRY switching where one custom property can update multiple properties. I won’t go into the details here, but our code can be revised like this:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     calc(200% - var(--i, 0) * 100%) 100% / 200% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - calc(var(--i, 0) * .3s)); } .hover-2:hover {   --i: 1; }

The --i custom property is initially undefined, so the fallback value, 0, is used. On hover though, we replace 0 with 1. You can do the math for both cases and get the values for each one. You can see that variable as a “switch” that update all our values at once on hover.

Again, we’re back to only three declarations for a pretty cool hover effect!

Hover effect #3

We are going to use two gradients instead of one for this effect. We will see that combining multiple gradients is another way to create fancy hover effects.

Here’s a diagram of what we’re doing:

We initially have two gradients that overflow the element so that they are out of view. Each one has a fixed height and toes up half of the element’s width. Then we slide them into view to make them visible. The first gradient is placed at the bottom-left and the second one at the top-right. Finally, we increase the height to cover the whole element.

Here’s how that looks in CSS:

.hover-3 {   background-image:     linear-gradient(#1095c1 0 0),     linear-gradient(#1095c1 0 0);   background-repeat: no-repeat;   background-size: 50% .08em;   background-position:     -100% 100%,     200% 0;   transition: background-size .3s, background-position .3s .3s; } .hover-3:hover {   background-size: 50% 100%;   background-position:     0 100%,     100% 0;     transition: background-size .3s .3s, background-position .3s; }

The code is almost the same as the other hover effects we’ve covered. The only difference is that we have two gradients with two different positions. The position values may look strange but, again, that’s related to how percentages work with the background-position property in CSS, so I highly recommend reading my Stack Overflow answer if you want to get into the gritty details.

Now let’s optimize! You get the idea by now — we’re using shorthand properties, custom properties, and calc() to tidy things up.

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--p, 0%)) 100% / 50% var(--p, .08em),     var(--c) calc( 200% - var(--p, 0%)) 0    / 50% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-3:hover {   --p: 100%;   --t: 0.3s; }

I have added an extra custom property, --c, that defines the gradient since the same gradient is used in both places.

I am using 50.1% in that demo instead of 50% for the background size because it prevents a gap from showing between the gradients. I also added 1% to the positions for similar reasons.

Let’s do the second optimization by using the switch variable:

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--i, 0) * 100%) 100% / 50% calc(100% * var(--i, 0) + .08em),     var(--c) calc( 200% - var(--i, 0) * 100%) 0 / 50% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - var(--i, 0) * .3s); } .hover-3:hover {   --i: 1; }

Are you started to see the patterns here? It’s not so much that the effects we’re making are difficult. It’s more the “final step” of code optimization. We start by writing verbose code with a lot of properties, then reduce it following simple rules (e.g. using shorthand, removing default values, avoiding redundant values, etc) to simplify things down as much as possible.

Hover effect #4

I will raise the difficulty level for this last effect, but you know enough from the other examples that I doubt you’ll have any issues with this one.

This hover effect relies on two conic gradients and more calculations.

Initially, we have both gradients with zero dimensions in Step 1. We increase the size of each one in Step 2. We keep increasing their widths until they fully cover the element, as shown in Step 3. After that, we slide them to the bottom to update their position. This is the “magic” part of the hover effect. Since both gradients will use the same coloration, changing their position in Step 4 will make no visual difference — but we will see a difference once we reduce the size on mouse out during Step 5.

If you compare Step 2 and Step 5, you can see that we have a different inclination. Let’s translate that into code:

.hover-4 {   background-image:     conic-gradient(/* ??? */),     conic-gradient(/* ??? */);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The positions are pretty clear. One gradient starts at top left (0 0) and ends at bottom left (0 100%) while the other starts at top right (100% 0) and ends at bottom right (100% 100%).

We’re using a transition on the background positions and sizes to reveal them. We only need a transition value for the background-size. And like before, background-position needs to change instantly, so we’re assigning a 0s value for the transition’s duration.

For the sizes, both gradient need to have 0 width and twice the element height (0% 200%). We will see later how their sizes change on hover. Let’s first define the gradient configuration.

The diagram below illustrates the configuration of each gradient:

Note that for the second gradient (indicated in green), we need to know the height to use it inside the conic-gradient we’re creating. For this reason, I am going to add a line-height that sets the element’s height and then try that same value for the conic gradient values we left out.

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background-image:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0),     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The last thing we have left is to figure out the background’s size. Intuitively, we may think that each gradient needs to take up half of the element’s width but that’s actually not enough.

We’re left with a large gap if we use 50% as the background-size value for both gradients.

We get a gap equal to the height, so we actually need to do is increase the size of each gradient by half the height on hover for them to cover the whole element.

.hover-4:hover {   background-size: calc(50% + .6em) 200%;   background-position:     0 100%,     100% 100%; }

Here’s what we get after optimizing them like the previous examples:

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0)        0  var(--p, 0%) / var(--s, 0%) 200% no-repeat,     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0)        100% var(--p, 0%) / var(--s, 0%) 200% no-repeat;   transition: .4s, background-position 0s; } .hover-4:hover {   --p: 100%;   --s: calc(50% + .6em); } 

What about the version with only one custom property?

I will leave that for you! After looking at four similar hover effects, you should be able to get the final optimization down to a single custom property. Share your work in the comment section! There’s no prize, but we may end up with different implementations and ideas that benefit everyone!

Before we end, let me share a version of that last hover effect that Ana Tudor cooked up. It’s an improvement! But note that it lacks Firefox supports due to a known bug. Still, it’s a great idea that shows how to combine gradients with blend modes to create even cooler hover effects.

Wrapping up

We made four super cool hover effects! And even though they are different effects, they all take the same approach of using CSS background properties, custom properties, and calc(). Different combinations allowed us to make different versions, all using the same techniques that leave us with clean, maintainable code.

If you want to get some ideas, I made a collection of 500 (yes, 500!) hover effects, 400 of which are done without pseudo-elements. The four we covered in this article are just the tip of the iceberg!


Cool Hover Effects That Use Background Properties originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

6 Creative Ideas for CSS Link Hover Effects

Creating CSS link hover effects can add a bit of flair to an otherwise bland webpage. If you’ve ever found yourself stumped trying to make a slick hover effect, then I have six CSS effects for you to take and use for your next project.

A default link hover effect above a styled link hover effect with a rainbow underline.

Let’s get right to it!

I know we’re talking about :hover and all, but it can sometimes (but maybe not always) be a good idea lump :focus in as well, as not all interactions are directly from a mouse, but perhaps a tap or keystroke.

This effect applies a box shadow to the inline link, altering the color of the link text in the process. We start with padding all around the link, then add a negative margin of the same value to prevent the padding from disrupting the text flow.

We will use box-shadow instead of the background property since it allows us to transition.

a {   box-shadow: inset 0 0 0 0 #54b3d6;   color: #54b3d6;   margin: 0 -.25rem;   padding: 0 .25rem;   transition: color .3s ease-in-out, box-shadow .3s ease-in-out; } a:hover {   box-shadow: inset 100px 0 0 0 #54b3d6;   color: white; }

Here’s a fun one where we swap the text of the link with some other text on hover. Hover over the text and the linked text slides out as new text slides in.

Easier to show than tell.

There’s quite a bit of trickery happening in this link hover effect. But the magic sauce is using a data-attribute to define the text that slides in and call it with the content property of the link’s ::after pseudo-element.

First off, the HTML markup:

<p>Hover <a href="#" data-replace="get a link"><span>get a link</span></a></p>

That’s a lot of inline markup, but you’re looking at a paragraph tag that contains a link and a span.

Let’s give link some base styles. We need to give it relative positioning to hold the pseudo-elements — which will be absolutely positioned — in place, make sure it’s displayed as inline-block to get box element styling affordances, and hide any overflow the pseudo-elements might cause.

a {   overflow: hidden;   position: relative;   display: inline-block; }

The ::before and ::after pseudo-elements should have some absolute positioning so they stack with the actual link. We’ll make sure they are set to the link’s full width with a zero offset in the left position, setting them up for some sliding action.

a::before, a::after {  content: '';   position: absolute;   width: 100%;   left: 0; }

The ::after pseudo-element gets the content from the link’s data-attribute that’s in the HTML markup:

a::after {   content: attr(data-replace); }

Now we can transform: translate3d() the ::after pseudo-element element to the right by 200%. We move it back into position on :hover. While we’re at it, we can give this a zero offset n the top direction. This’ll be important later when we use the ::before pseudo-element like an underline below the text.

a::after {   content: attr(data-replace);   top: 0;   transform-origin: 100% 50%;   transform: translate3d(200%, 0, 0); }  a:hover::after, a:focus::after {   transform: translate3d(0, 0, 0); }

We’re also going to transform: scale() the ::before pseudo-element so it’s hidden by default, then scale it back up on :hover. We’ll make it small, like 2px in height, and pin it to the bottom so it looks like an underline on the text that swaps in with ::after.

a::before {   background-color: #54b3d6;   height: 2px;   bottom: 0;   transform-origin: 100% 50%;   transform: scaleX(0); }  a:hover::before, a:focus::before {   transform-origin: 0% 50%;   transform: scaleX(1); }

The rest is all preference! We drop in a transition on the transform effects, some colors, and whatnot to get the full effect. Those values are totally up to you.

View full CSS
a {   overflow: hidden;   position: relative;   display: inline-block; }  a::before, a::after {  content: '';   position: absolute;   width: 100%;   left: 0; } a::before {   background-color: #54b3d6;   height: 2px;   bottom: 0;   transform-origin: 100% 50%;   transform: scaleX(0);   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1); } a::after {   content: attr(data-replace);   height: 100%;   top: 0;   transform-origin: 100% 50%;   transform: translate3d(200%, 0, 0);   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1);   color: #54b3d6; }  a:hover::before {   transform-origin: 0% 50%;   transform: scaleX(1); } a:hover::after {   transform: translate3d(0, 0, 0); }  a span {   display: inline-block;   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1); }  a:hover span {   transform: translate3d(-200%, 0, 0); }

This is a pretty popular effect I’ve seen used in quite a few places. The idea is that you use the link’s ::before pseudo-element as a thick underline that sits slightly behind the actual text of the link. Then, on hover, the pseudo-element expands to cover the whole thing.

OK, some base styles for the link. We want no text-decoration since ::before will act like one, then some relative positioning to hold ::before in place when we give that absolute positioning.

a {   text-decoration: none;   position: relative; }

Now let’s set up ::before by making it something like 8px tall so it looks like a thick underline. We’ll also give it absolute positioning so we have control to make it the full width of the actual link while offsetting it so it’s at the left and is just a smidge off the bottom so it looks like it’s subtly highlighting the link. May as well give it z-index: -1 so we’re assured it sits behind the link.

a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1; }

Nice, nice. Let’s make it appear as though ::before is growing when the link is hovered. All we need is to change the height from 3px to 100%. Notice that I’m also dropping the bottom offset back to zero so the background covers more space when it grows.

a:hover::before {   bottom: 0;   height: 100%; }

Now for slight transition on those changes:

a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1;   transition: all .3s ease-in-out; }
View full CSS
a {   text-decoration: none;   color: #18272F;   font-weight: 700;   position: relative; }  a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1;   transition: all .3s ease-in-out; }  a:hover::before {   bottom: 0;   height: 100%; }

I personally like using this effect for links in a navigation. The link starts in one color without an underline. Then, on hover, a new color slides in from the right while an underline slides in from the left.

Neat, right? There’s a lot of motion happening in there, so you might consider the accessibility implications and wrap it all in a prefers-reduced-motion query to replace it with something more subtle for those with motion sensitivities.

Here’s how it works. We give the link a linear background gradient with a hard stop between two colors at the halfway mark.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   ); }

We make the background double the link’s width, or 200%, and position it all the way over to the left. That way, it’s like only one of the gradients two colors is showing.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%; }

The magic happens when we reach for a couple of non-standard -webkit-prefixed properties. One strips the color out of the text to make it transparent. The other clips the background gradient to the text so it appears the text is actually the color of the background.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent; }

Still with me? Now let’s make the link’s faux underline by putting ::before to use. We’ll give it the same color we gave the on the hidden portion of the link’s background gradient and position it under the actual link so it looks like a proper text-decoration: underline.

a:before {   content: '';   background: #54b3d6;   display: block;   position: absolute;   bottom: -3px;   left: 0;   width: 0;   height: 3px; }

On hover, we slide ::before into place, coming in from the left:

a:hover {  background-position: 0; }

Now, this is a little tricky. On hover, we make the link’s ::before pseudo-element 100% of the link’s width. If we were to apply this directly to the link’s hover, we’d make the link itself full-width, which moves it around the screen. Yikes!

a:hover::before {   width: 100%; }

Add a little transition to smooth things out:

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   transition: all 0.3s ease-in-out; }
View full CSS
a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   display: inline-block;   padding: 5px 0;   position: relative;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   transition: all 0.3s ease-in-out; }  a:before {   content: '';   background: #54b3d6;   display: block;   position: absolute;   bottom: -3px;   left: 0;   width: 0;   height: 3px;   transition: all 0.3s ease-in-out; }  a:hover {  background-position: 0; }  a:hover::before {   width:100%; }

We can’t do text-decoration-color: rainbow, but we can fake it with a little background magic mixed with linear gradients.

First, we remove the link’s text-decoration:

a {   text-decoration: none; }

Now for those gradients. We chain two linear gradients together on the same background property. One gradient is the initial color before hover. The second is the rainbow on hover.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   ); }

Let’s make the background size a mere 3px tall so it looks like, you know, an underline. We can size both gradients together on the background-size property so that the initial gradient is full width and 3px tall, and the rainbow is zero width.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px; }

Now we can position the background gradients — at the same time on the background-position property — so that the first gradient is fully in view and the rainbow is pushed out of view. Oh, and let’s make sure the background isn’t repeating while we’re at it.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px;   background-position: 100% 100%, 0 100%;   background-repeat: no-repeat; }

Let’s update the background-size on hover so that the gradients swap values:

a:hover {   background-size: 0 3px, 100% 3px; }

And, finally, a little transition when the hover takes place:

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px;   background-position: 100% 100%, 0 100%;   background-repeat: no-repeat;   transition: background-size 400ms; }

Voilà!

Geoff Graham actually covered this same one recently when he dissected Adam Argyle’s slick hover effect. In his demo, a background color enters from the left behind the link, then exits to the right on mouse out.

My version pares down the background so it’s more of an underline.

a {   position: relative; }  a::before {     content: '';     position: absolute;     width: 100%;     height: 4px;     border-radius: 4px;     background-color: #18272F;     bottom: 0;     left: 0;     transform-origin: right;     transform: scaleX(0);     transition: transform .3s ease-in-out;   }  a:hover::before {   transform-origin: left;   transform: scaleX(1); }

That’s not the only way to accomplish this! Here’s another one by Justin Wong using background instead:

Geoff also has a roundup of CSS link hover effects, ranging from neat to downright absurd. Worth checking out!

Have a blast linking!

There are a lot of options when it comes to creating your own hover effect for in-line links with CSS. You can even play with these effects and create something new. I hope you liked the article. Keep experimenting!


6 Creative Ideas for CSS Link Hover Effects originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Adam Argyle’s Sick Mouse-Out CSS Hover Effect

I was killing some time browsing my CodePen feed for some eye candy and didn’t need to go past the first page before spotting a neat CSS hover effect by Adam Argyle.

I must’ve spent 10 minutes just staring at the demo in awe. There’s something about this that feels so app-like. I think it might be how contextually accurate it is in that the background color slides in from the left, then exits out through the right. It’s exactly the sort of behavior I’d expect from a mouse-in, mouse-out sort of interaction.

Whatever the case, I fired up a fresh pen and went to work recreating it. And it’s not super complex or anything, but rather a clever use of transitions and transforms paired with proper offsets. Quite elegant! I’m actually a little embarrassed how long it took me to realize how the mouse-out part works.

Here’s how I tackled it, warts and all.

“I bet that’s using a transition on a background.”

That was my first thought. Define the background-color, set the background-size and background-position, then transition the background-position. That’s how I’ve seen that “growing” background color thing done in the past. I’ve done that myself on some projects, like this:

If I could do the same thing, only from left-to-right, then all that’s left is the mouse-out, right? Nope. The problem is there’s nothing that can really make the background-position transition from left-to-right to left-to-right. I could make it do one or the other, but not both.

“Maybe it’s a transform instead.”

My next attempt was jump into transforms. The transform property provides a bunch of functions that can transition together for slightly more complex movement. For example, the background can “grow” or “shrink” by changing the element’s scale(). Or, in this case, just along the x-axis with scaleX().

But like I mentioned, there isn’t a way to isolate the element’s background to do that. Going from scaleX(0) to scaleX(1) scales the entire element, so that basically squishes the link — content and all — down to nothing, then stretches it back out to its natural size which is a totally different effect. Plus, it means starting with scaleX(0) which hides the whole dang thing by default making it unusable.

But a pseudo-element could work! It doesn’t matter if that gets squished or hidden because it isn’t part of the actual content. Gotta put the background on that instead and position it directly under the link.

a {   /* Keeps the pseudo-element contained to the element */   position: relative; }  a::before {   background: #ff9800;   content: "";   inset: 0; /* Logical equivalent to physical offsets */   position: absolute;   transform: scaleX(0); /* Hide by default */   z-index: -1; /* Ensures the link is stacked on top */ }

“Now I need ::before to change on hover.”

I knew I could make ::before scale from 0 to 1 by chaining it to the link element’s :hover state.

a:hover::before {   transform: scaleX(1) }

Nice! I was onto something.

Sprinkle a little transition fairy dust on it and things start to come to life.

a::before {   background: #ff9800;   content: "";   inset: 0;   position: absolute;   transform: scaleX(0);   transition: transform .5s ease-in-out;   z-index: -1; }

“Hmm, the transition moves in both directions.”

Again, this is where I sorta got stuck. Something in my head just wasn’t clicking for some reason. As per usual, I ran over to the CSS-Tricks Almanac to see what property might’ve slipped my mind.

Ah, yes. That would be transform-origin. That allows me to set where the transform starts, which is not totally dissimilar from setting the background-position like I tried earlier. The transform could start from the left instead of its default 50% 50% position.

a::before {   background: #ff9800;   content: "";   inset: 0;   position: absolute;   transform: scaleX(0);   transform-origin: left;   transition: transform .5s ease-in-out;   z-index: -1; }

Yeah, like this:

I was already transitioning ::before to scaleX(1) on link hover. If I reversed the transform-origin from left to right at the same time, then mayyyybe the highlight goes out the opposite of how it came in when the mouse exits?

a:hover::before {   transform: scaleX(1);   transform-origin: right; }

🤞

Whoops, backwards! Let’s swap the left and right values. 🙃

Gorgeous. Thank you, Adam, for the inspiration!


Adam Argyle’s Sick Mouse-Out CSS Hover Effect originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Want to Write a Hover Effect With Inline CSS? Use CSS Variables.

The other day I was working on a blog where each post has a custom color attached to it for a little dose of personality. The author gets to pick that color in the CMS when they’re writing the post. Just a super-light layer of art direction.

To make that color show up on the front end, I wrote the value right into an inline style attribute on the <article> element. My templates happened to be in Liquid, but this would look similar in other templating languages:

{% for post in posts %} <article style="background: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article> {% endfor %}

No problem there. But then I thought, “Wouldn’t it be nice if the custom color only showed up when when hovering over the article card?” But you can’t write hover styles in a style attribute, right?

My first idea was to leave the style attribute in place and write CSS like this:

article {   background: lightgray !important; } article:hover {   /* Doesn't work! */   background: inherit; }

I can override the inline style by using !important, but there’s no way to undo that on hover.

Eventually, I decided I could use a style attribute to get the color value from the CMS, but instead of applying it right away, store it as a CSS variable:

<article style="--custom_color: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article>

Then, that variable can be used to define the hover style in regular CSS:

article {   background: lightgray; } article:hover {   /* Works! */   background: var(--custom_color); }

Now that the color value is saved as a CSS variable, there are all kinds of other things we can do with it. For instance, we could make all links in the post appear in the custom color:

article a {   color: var(--custom_color); }

And because the variable is scoped to the <article> element, it won’t affect anything else on the page. We can even display multiple posts on the same page where each one renders in its own custom color.

Browser support for CSS variables is pretty deep, with the exception of Internet Explorer. Anyway, just a neat little trick that might come in handy if you find yourself working with light art direction in a CMS, as well as a reminder of just how awesome CSS variables can be.


The post Want to Write a Hover Effect With Inline CSS? Use CSS Variables. appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Long Hover

I had a very embarrassing CSS moment the other day.

I was working on the front-end code of a design that had a narrow sidebar of icons. There isn’t enough room there to show text of what the icons are, so the idea is that we’ll use accessible (but visually hidden, by default) text that is in there already as a tooltip on a “long hover.” That is, a device with a cursor, and the cursor hovering over the element for a while, like three seconds.

So, my mind went like this…

  1. I can use state: the tooltip is either visible or not visible. I’ll manage the state, which will manifest in the DOM as a class name on an HTML element.
  2. Then I’ll deal with the logic for changing that state.
  3. The default state will be not visible, but if the mouse is inside the element for over three seconds, I’ll switch the state to visible.
  4. If the mouse ever leaves the element, the state will remain (or become) not visible.

This was a React project, so state was just on the mind. That ended up like this:

Not that bad, right? Eh. Having state managed in JavaScript does potentially open some doors, but in this case, it was total overkill. Aside from the fact that I find mouseenter and mouseleave a little finicky, CSS could have done the entire thing, and with less code.

That’s the embarrassing part… why would I reach up the chain to a JavaScript library to do this when the CSS that I’m already writing can handle it?

I’ll leave the UI in React, but rip out all the state management stuff. All I’ll do is add a transition-delay: 3s when the .icon is :hover so that it’s zero seconds when not hovered, then goes away immediately when the mouse cursor leaves).

A long hover is basically a one-liner in CSS:

.thing {   transition: 0.2s; } .thing:hover {   transision-delay: 3s; /* delay hover animation only ON, not OFF */ }

Works great.

One problem that isn’t addressed here is the touch screen problem. You could argue screen readers are OK with the accessible text and desktop browsers are OK because of the custom tooltips, but users with touch-only screens might be unable to discover the icon labels. In my case, I was building for a large screen scenario that assumes cursors, but I don’t think all-is-lost for touch screens. If the element is a link, the :hover might fire on first-tap anyway. If the link takes you somewhere with a clear title, that might be enough context. And you can always go back to more JavaScript and handle touch events.


The post Long Hover appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

Scrollbars on Hover

First, scrollbars are a usability and accessibility thing. Second, a rule of thumb: if an area scrolls, it should have a visible scrollbar. But the web is a big place and I like tricks, so I’m going to cover the idea of only revealing them on hover. Even macOS itself¹ hides scrollbars by default, revealing them contextually and on interaction. Same on iOS, leading to confusing momements.

All that aside, here’s a way to hide scrollbars by default, only revealing them when the element is hovered. It was created by Thomas Gladdines, who also emailed me about it:

In quick testing on my machine, it works across Chrome, Firefox, and Safari, regardless of my macOS settings. So pretty robust.

The trick is that mask covers the scrollbar! So, if you create a mask that is exactly as wide as the scrollbar (here, I’m just guessing that 17px will cover it) and super duper tall (both of which should probably be calculated by a script), it can perfectly cover the scrollbar. You can even transition the position of the mask, faking a fading in/out effect. Very clever.

Notably, this is the real scrollbar of the element, and not a faked one. Faking one could be another approach. Ben Nadel covered how Slack does that. Their trick is to force the scrollbar to render in an area hidden by overflow, and make a virtual scrollbar that mimics the native one (which you’d then have more direct control over). It’s not forcing the scrollbar either, which is something else you can do if so motivated. And nothing about this prevents you from styling the scrollbar, which might actually have some benefits like specifying the exact width of it.

  1. As I write: If your device allows gestures, scroll bars are hidden until you start scrolling. Otherwise, they’re visible. ↩️

The post Scrollbars on Hover appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

Bold on Hover… Without the Layout Shift

When you change the font-weight of a font, the text will typically cause a bit of a layout shift. That’s because bold text is often larger and takes up more space. Sometimes that doesn’t matter, like a vertical stack of links where the wider/bolder text doesn’t push anything anyway. Sometimes it does matter, like a horizontal row where the wider/bolder text pushes other elements away a smidge.

Ryan Mulligan demonstrates:

Ryan’s technique is very clever. Each item in the list has a pseudo-element on it with the exact text in the link. That pseudo-element is visually hidden, but pre-bolded and still occupies width. So when the actual link text is bolded, it won’t take up any additional width.

It also sorta depends on how you’re doing the layout. Here, if I force four columns with CSS grid and text that doesn’t really challenge the width, the bolding doesn’t affect the layout either:

But if I were to, say, let those links flow into automatic columns, we would have the shifting problem.


The post Bold on Hover… Without the Layout Shift appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

4 Ways to Animate the Color of a Text Link on Hover

Let’s create a pure CSS effect that changes the color of a text link on hover… but slide that new color in instead of simply swapping colors.

There are four different techniques we can use to do this. Let’s look at those while being mindful of important things, like accessibility, performance, and browser support in mind.

Let’s get started!

Technique 1: Using background-clip: text

At the time of writing, the background-clip: text property is an experimental feature and is not supported in Internet Explorer 11 and below.

This technique involves creating knockout text with a hard stop gradient. The markup consists of a single HTML link (<a>) element to create a hyperlink:

<a href="#">Link Hover</a>

We can start adding styles to the hyperlink. Using overflow: hidden will clip any content outside of the hyperlink during the hover transition:

a {   position: relative;   display: inline-block;   font-size: 2em;   font-weight: 800;   color: royalblue;   overflow: hidden; }

We will need to use a linear gradient with a hard stop at 50% to the starting color we want the link to be as well as the color that it will change to:

a {   /* Same as before */   background: linear-gradient(to right, midnightblue, midnightblue 50%, royalblue 50%); }

Let’s use background-clip to clip the gradient and the text value to display the text. We will also use the background-size and background-position properties to have the starting color appear:

a {   /* Same as before */   background-clip: text;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   background-size: 200% 100%;   background-position: 100%; }

Finally, let’s add the transition CSS property and :hover CSS pseudo-class to the hyperlink. To have the link fill from left to right on hover, use the background-position property:

a {   /* Same as before */   transition: background-position 275ms ease; } a:hover {   background-position: 0 100%; }

While this technique does achieve the hover effect, Safari and Chrome will clip text decorations and shadows, meaning they won’t be displayed. Applying text styles, such as an underline, with the text-decoration CSS property will not work. Perhaps consider using other approaches when creating underlines.

Technique 2: Using width/height

This works by using a data attribute containing the same text as the one in the <a> tag and setting the width (filling the text from left-to-right or right-to-left) or height (filling the text from top-to-bottom or bottom-to-top), from 0% to 100% on hover.

Here is the markup:

<a href="#" data-content="Link Hover">Link Hover</a>

The CSS is similar to the previous technique minus the background CSS properties. The text-decoration property will work here:

a {   position: relative;   display: inline-block;   font-size: 2em;   color: royalblue;   font-weight: 800;   text-decoration: underline;   overflow: hidden; }

This is when we need to use the content from the data-content attribute. It will be positioned above the content in the <a> tag. We get to use the nice little trick of copying the text in the data attribute and displaying it via the attr() function on the content property of the element’s ::before pseudo-element.

a::before {   position: absolute;   content: attr(data-content); /* Prints the value of the attribute */   top: 0;   left: 0;   color: midnightblue;   text-decoration: underline;   overflow: hidden;   transition: width 275ms ease; }

To keep the text from wrapping to the next line, white-space: nowrap will be applied. To change the link fill color, set the value for the color CSS property using the ::before pseudo-element and having the width start at 0:

a::before {   /* Same as before */   width: 0;   white-space: nowrap; }

Increase the width to 100% to the ::before pseudo element to complete the text effect on hover:

a:hover::before {   width: 100%; }

While this technique does the trick, using the width or height properties will not produce a performant CSS transition. It is best to use either the transform or opacity properties to achieve a smooth, 60fps transition.

Using the text-decoration CSS property can allow for different underline styles to appear in the CSS transition. I created a demo showcasing this using the next technique: the clip-path CSS property.

Technique 3: Using clip-path

For this technique, we will be using the clip-path CSS property with a polygon shape. The polygon will have four vertices, with two of them expanding to the right on hover:

The markup is the same as the previous technique. We will use a ::before pseudo-element again, but the CSS is different:

a::before {   position: absolute;   content: attr(data-content);   color: midnightblue;   text-decoration: underline;   clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);   transition: clip-path 275ms ease; }

Unlike the previous techniques, text-decoration: underline must be declared to the ::before pseudo-element for the color to fill the underline on hover.

Now let’s look into the CSS for the clip-path technique:

clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);

The polygon’s vertices of the clip-path property are set in percentages to define coordinates by the order written:

  • 0 0 = top left
  • 0 0 = top right
  • 100% 0 = bottom right
  • 0 100% = bottom left

The direction of the fill effect can be changed by modifying the coordinates. Now that we have an idea for the coordinates, we can make the polygon expand to the right on hover:

a:hover::before {   clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }

This technique works pretty well, but note that support for the clip-path property varies between browsers. Creating a CSS transition with clip-path is a better alternative than using the width/height technique; however, it does affect the browser paint.

Technique 4: Using transform

The markup for this technique uses a masking method with a <span> element. Since we will be using duplicated content in a separate element, we will use aria-hidden="true" to improve accessibility — that will hide it from screen readers so the content isn’t read twice:

<a href="#"><span data-content="Link Hover" aria-hidden="true"></span>Link Hover</a>

The CSS for the <span> element contains a transition that will be starting from the left:

span {   position: absolute;   top: 0;   left: 0;   overflow: hidden;   transform: translateX(-100%);   transition: transform 275ms ease; }

Next, we need to get the <span> to slide the right like this:

To do this, we will use the translateX() CSS function and set it to 0:

a:hover span {   transform: translateX(0); }

Then, we will use the ::before pseudo-element for the <span>, again using the data-content attribute we did before. We’ll set the position by translating it 100% along the x-axis.

span::before {    display: inline-block;   content: attr(data-content);   color: midnightblue;   transform: translateX(100%);   transition: transform 275ms ease;   text-decoration: underline; }

Much like the <span> element, the position of the ::before pseudo-element will also be set to  translateX(0):

a:hover span::before {   transform: translateX(0); }

While this technique is the the most cross-browser compatible of the bunch, it requires more markup and CSS to get there. That said, using the transform CSS property is great for performance as it does not trigger repaints and thus produces smooth, 60fps CSS transitions.

There we have it!

We just looked at four different techniques to achieve the same effect. Although each has its pros and cons, you can see that it’s totally possible to slide in a color change on text. It’s a neat little effect that makes links feel a little more interactive.

The post 4 Ways to Animate the Color of a Text Link on Hover appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]