Exploration: Duolingo Button Animation

Recently, I’ve been really fascinated by the detail of Duolingo’s UI, especially the UI animation. I noticed even the buttons’ animations feels smooth. I was motivated to recreate the button and learn along the way.

So, here’s what I built for this post:

In this post, I’ll share how I made the button styles using HTML and CSS.

Let’s build!!! 🔨

First Step: HTML, CSS, and the initial looks

First, let’s start with a simple HTML button element with styles:

<!-- HTML -->
<button class="button">Click Me</button>
/* CSS */
:root {
  --color-primary: #4dabf7;
  --color-primary-hover: #339af0;
  --color-shadow: #1c7ed6;
  --color-text: #fff;
}

.button {
  background-color: var(--color-primary);
  color: var(--color-text);
  font-weight: 700;
  letter-spacing: 0.5px;
  border-radius: 10px;
  padding: 0.5rem 1rem;
}

I added CSS variables to make managing colors easier.

However this is not the initial appearance we want for the component we’re aiming to build. The Duolingo button has a “depth” effect. We could achieve the looks by using several approaches. For instance, we may attempt using the border-bottom-width property to add the depth effect.

Personally, I don’t think that’s a good idea. The border property will push other elements and the changes made to its value will also affected the whole layout. So, I decided to use the box-shadow and transform properties instead:

/* CSS */
:root {
  /* ... */
}

.button {
  /* ... */
  /* Styles for adding depth: */
  box-shadow: 0px 4px var(--color-shadow);
  transform: translateY(-4px);
}

The box-shadow property is used to create the “depth” effect; it’s the part that draw the button’s z-axis part. Tha transform property is used to lift the button from its normal inline flow.

Second Step: Adding hover state styles

The first interaction I wanted to add is the hover state. I chose the hover state simply because it’s the first state that will be triggered when the user intended to click the button.

We can use just add a :hover pseudo-class to our CSS to add the hover effect:

/* CSS */
:root {
  /* ... */
}
.button {
  /* ... */
}

/* Hover styles */
.button:hover {
  background-color: var(--color-primary-hover);
  cursor: pointer;
}

Last Step: Adding active state styles

The final step — the most exiciting step — is to add the active state’s styles!

First, let’s add the pseudo-class :active to the .button class to add styles when the button is clicked:

/* CSS */
:root {
  /* ... */
}
.button {
  /* ... */
}
.button:hover {
  /* ... */
}

/* Clicked (active) styles */
.button:active {
  box-shadow: 0px 0px var(--color-shadow);
  background-color: var(--color-primary-hover);
  transform: translateY(0px);
}

Looks good! So, we change the box-shadow and transform properties. First, the box-shadow’s value is changed so we create an effect of pressed down. Then, the transform value also changed to give a sense that the button pressed down to it’s normal inline flow.

However, personally, I find the transition to be slightly odd. Let’s add the final touch — animation:

/* CSS */
:root {
  /* ... */
}
.button {
  /* ... */
}
.button:hover {
  /* ... */
}

/* Clicked (active) styles */
.button:active {
  /* ... */
  animation: buttonClicked 0.15s;
}

@keyframes buttonClicked {
  0% {
    transform: translateY(-4px);
    box-shadow: 0px 4px var(--color-shadow);
  }
  100% {
    transform: translateY(0px);
    box-shadow: 0px 0px var(--color-shadow);
  }
}

Perfect! 💯💯💯

Conclusion

To wrap up, we’ve recreated Duolingo’s button using HTML, CSS, and CSS Animation. I hope you find benefit from this short post. Thanks!