How to create a morphing button using only css

Ankit Karnany
The Observables
Published in
6 min readOct 29, 2019
Photo by Joanna Kosinska on Unsplash

Buttons. Being one of the most important components in web design, they allow our users to complete a certain task. The style of button has changed from text links to skeuomorphism to flat design. In this lesson, we’ll learn how to create a morphing button like the one shown below. We’ll mainly learn about the important concepts used to create such animations.

Let’s get started.

HTML

Based on our observation of the above buttons, we can easily conclude that the html template of the button would have a wrapper button comprising of a text and an arrow. So, let us assume that the html of this button will look something like this —

<div class="circle">  <div class="text">Next</div>  <div class="arrow"><i class="fas fa-arrow-right"></i></div></div>

Please note that I am using a font awesome icon for the right arrow. You can replace that with your own icon.

We are good with our html. Let’s move on to the interesting part.

CSS

Let’s give the btn-morph the basic styling like size and background-color.

.btn-morph {
width: 60px; // note that width and height are same
height: 60px;
background-color: #EF5350; // the red color used in above button
}

Our button should look something like this —

Now we want to make the button look like a circle. To achieve that, we give it a border-radius of 60px. If you look in the previous code, the width and height of the button is also 60px. For a block to look like a circle after applying a border-radius , the width and the height of the block must be same. Here we are applying the border-radius to be the same as width and height.

After adding this property —

.btn-morph {
width: 60px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
}

It turns out to be something like this —

Now, let us position the arrow inside the button. To position the arrow, we use the position: absolute; with top: 50%; and left: 50%;

.btn-morph {
width: 60px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative; // for absolute to work in child the parent
// should have a position: relative;

}
.arrow {
position: absolute;
top: 50%;
right: 50%;
}

Here is our result —

If you are wondering why the arrow is not in the centre of the circle, the top-right corner of the arrow is in the centre of the circle. To move the arrow to the centre, we would naturally use transform: translate(50%, -50%); because the percent inside the translate moves the arrow considering the centre of the arrow as the origin. So, we would change our style to —

.btn-morph {
width: 60px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative;
}
.arrow {
position: absolute;
top: 50%;
right: 50%;
transform: translate(50%, -50%); // x = 50%, y = -50%
}

As a result, we get —

Now, let us increase the width of the button to position the text as well. Post that, we’ll change the width of button back to 60px and create an animation to increase the width instead.

When I increase the width of the button to 120px , the arrow moves to the centre of the button. To fix that, we need to use an absolute value for the right: 50%; of the arrow. It gets fixed, when I change right to 30px So I modified the code to be —

.btn-morph {
width: 120px; // Increased the width of the button
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative;
}
.arrow {
position: absolute;
top: 50%;
right: 30px; // Fixed the right to absolute value
transform: translate(50%, -50%);
}

Our button now looks like this —

To align the .text We use the similar method. Here is the styling for the same —

.btn-morph {
width: 120px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative;
}
.arrow {
position: absolute;
top: 50%;
right: 30px;
transform: translate(50%, -50%);
}
.text {
position: absolute;
top: 50%;
right: 60px; // We are using this instead of left because it will
// grow to the left. So it should have a fixed
// distance from right
transform: translateY(-50%); // Similar to the arrow centring
}

This is how the button looks with the text —

Now, we can change the width of the button back to 60px.

.btn-morph {
width: 60px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative;
}
.arrow {
position: absolute;
top: 50%;
right: 30px;
transform: translate(50%, -50%);
}
.text {
position: absolute;
top: 50%;
right: 60px;
transform: translateY(-50%);
}

The result looks fine, but there is a problem here. We don’t see the text because it’s on a white background. The text should not be visible. This is how it looks when I select the text in the white background —

To hide this text we use a css property called overflow: hidden; . The div to which we give the overflow: hidden; hides the child if it tries to go outside the dimensions of the parent. And the child becomes visible only when the parent has enough space to incorporate the child.

Animation

It’s time to animate the width of the button on hover. For this, we use the transition: width 0.3s ease; and increase the width to 120px on hover event. So, our code now looks like this —

.btn-morph {
width: 60px;
height: 60px;
background-color: #EF5350;
border-radius: 60px;
color: white;
position: relative;
overflow: hidden; // hiding text when it is
// not in parent's viewport
transition: width 0.3s ease-in;

}
.btn-morph:hover {
width: 120px;
}
.arrow {
position: absolute;
top: 50%;
right: 30px;
transform: translate(50%, -50%);
}
.text {
position: absolute;
top: 50%;
right: 60px;
transform: translateY(-50%);
}

Almost there!

The last important thing to fix here is that the width is growing from the centre. But, we need it to grow only from right to left. To achieve that we can use position: absolute; and right: 0; to fix the button to the right. So, now when it tries to grow horizontally, it has to grow towards left side only because right position is fixed. For this we need to wrap this button inside a parent and give it a position: relative;

Thus giving us the following affect —

Now we can customize this button according to our taste. We can use different scales for the arrow icon, use different ease functions for various animations, etc.

Please find the link to the complete code here codepen.

Special credits for this story to Ricardo Mendieta

Thanks for reading! I hope you learned something.

--

--