
Gradient Transitions with CSS
I was recently working on a personal project and trying to improve my CSS along the way. I’m very new to the magic of CSS and I’ve most recently been playing around with transform and transitions. I thought it would be cool to transition an elements background to a gradient when you hovered over it. But to my surprise, my gradient wouldn’t transition! It would just suddenly appear and disappear. I assumed it was a typo, but my code seemed to look fine. All my other transitions were still working normally too. So it was off to Google where I found, lo and behold, that gradients don’t transition! Womp womp! Luckily there is plenty of information out there on how to get around the problem.
The secret is to lay a new element on top of the original element, with opacity of the new element set to 0. Then upon hovering, transition the new element opacity to 1. This way you can have the gradient on the invisible element, and it will “magically” appear when hovered over. Sounds simple enough, but for a rookie like me it’s not the most intuitive process in the world.
In my example code I will be using a div that holds a restaurant listing as an example.
I learned a good way to create the gradient element is to use the CSS pseudo element creator ::before. This adds a pseudo child element to the parent element it is called on. This child will hold our gradient. To set up the relationship of the two elements, you will need to set the position of the parent element to relative, and the new child element to absolute. This way you can place the child element directly on the parent element. This also allows you to make the child element the same size as the parent, simply by setting top, left, bottom, and right to 0.
.restaurant-listing {
position: relative;
z-index: 1; /* We'll come back to this */
}.restaurant-listing::before{
position: absolute;
content: "";
top: 0;
right: 0;
bottom: 0;
left: 0;
}
On the child element you add in your gradient as you normally would. You also have to set content to “”. This is because content for a pseudo element, such as ::before or ::after defaults to none, in which case the element would not be generated. Then you add your opacity transition property to the child, add your gradient, and set the child’s opacity to 0.
.restaurant-listing::before{
position: absolute;
content: "";
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgb(172,80,242);
background: linear-gradient(20deg, rgba(172,80,242,1) 15%, rgba(43,171,197,1) 87%);
transition: opacity 0.5s linear;
z-index: -1; /* We'll come back to this */
opacity: 0;
}
You then need to create a hover selector for your child element, that will set the opacity to 1.
.restaurant-listing:hover::before {
opacity: 1;
}
And lastly, the most confusing part of all. You need the child’s background to appear in front of the parent’s background, but behind the parent’s content. This can be achieved through z-indexing. If you set the parent’s z-index to 1, and the child’s z-index to -1, the proper effect will be displayed. This seems a little counter-intuitive, that a background with the lower z index will be displayed in front of the background with the higher index. This may in fact send you spiraling down a rabbit hole of z-index and stack context blog posts. Don’t ask me how I know. As I’m still trying to figure out exactly why this works, an explanation will have to wait for a future blog post. Long story short though, it is directly related to the stack context that is created for positioned elements, elements with a z-index property, and elements that have a set opacity.
All together your classes may look something like this:
.restaurant-listing {
position: relative;
width: 41%;
height: min-content;
background: rgb(43,171,197);
border-style: ridge;
border-width: 5px;
border-color: #860cf5;
border-radius: 5px;
margin: 2%;
padding-left: 2%;
transition: box-shadow 1s;
z-index: 1;
}.restaurant-listing:hover{
box-shadow: 5px 5px 5px 1px #0000007a;
}.restaurant-listing::before{
position: absolute;
content: "";
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgb(172,80,242);
background: linear-gradient(20deg, rgba(172,80,242,1) 15%, rgba(43,171,197,1) 87%);
transition: opacity 0.5s linear;
z-index: -1;
opacity: 0;
}.restaurant-listing:hover::before {
opacity: 1;
}
All this combines together to create a smooth “transition” into, from, or between gradients.
Sources:
Transitioning Gradients by Keith J. Grant
What properties create stacking context?
What You May Not Know About the Z-Index Property by Steven Bradley