This post describes how a simplified ripple effect can be achieved with minimal CSS and without using pseudo elements. If you are interested in enhancing the UX of your web app a little bit with close to zero effort keep reading!

Click the button to see it ripple:

❗️Also, I have a collection of 12 Pure CSS Material Components.

The ripple effect

The ripple effect is the phenomenon of dropping something like a stone into still water. In loose interpretation of Material Design this is mimicked with a circular highlight that is fading while growing from zero to maximum radius. This effect is triggered by touch and click events:

Connect user input to surface reactions with touch ripples.

Material Design Guidelines

The idea

The circle shape can be created with a simple radial-gradient. Gradients are not animatable, so the circle cannot be transitioned into another radial-gradient to make it grow. However, background-size is animatable, so defining a circle with a radius of 1% for example, then transitioning the background-size makes the circle size transition.

The other part of the effect is the fade of the highlight. The background-color property is also animatable, so it can be used to implement the fading transition. The transparent parts of the background-image reveal the background-color since the image is drawn atop of the color. Therefore the circle has to be defined transparent in the gradient to make it look like it is fading.

This implementation of button with ripple has three states: normal, :hover and :active. The ripple is triggered when the state changes from active to hover, that is when the click or tap is released. The last detail is that the area around the circle should be filled with the color of the :hover state.

Human readable states

Maybe it is easier to understand the implementation when described less technically.

In normal state no gradient is displayed, only the solid color of the background is visible.

The hover state changes the color of the background to the hover color (a lighter one), and adds a gradient. The gradient defines a small transparent circle in the middle, and the rest is filled with the hover color. The size of the background is increased so the size of the circle is actually larger than the button.

Finally the active state changes the color of the background to the active color (an even lighter one), and reduces the size of the background to normal. This is perceived as the button is solidly filled with the hover color, except that there is a small lighter dot in the middle.

The trick is that the transitions only happen when the state changes from active to hover. They make the small dot in the middle grow to the full size of the button, and darken its color from the active to hover color.

Limitations

One very obvious limitation is that the ripple will always start from the center of the button. The true ripple effect starts from the point of the interaction, where the user clicks or taps. This would need the tracking of mouse or touch coordinates which is not possible in CSS.

Another constraint is that background-size in Internet Explorer is not animatable (in Edge it is!). So in IE it gracefully degrades to full button highlight in active state.

The takeway: snippets

In CSS or PostCSS:

Replace var(--focus-color) and var(--active-color) strings with your actual color values respectfully if you are not using a preprocessor.

  .ripple {
  background-position: center;
  transition: background 0.5s;
}
.ripple:hover {
  background: var(--focus-color) radial-gradient(circle, transparent 1%, var(--focus-color) 1%) center/15000%;
}
.ripple:active {
  background-color: var(--active-color);
  background-size: 100%;
  transition: background 0s;
}

In LESS:

  .ripple {
  background-position: center;
  transition: background 0.5s;
  &:hover {
    background: @focus-color radial-gradient(circle, transparent 1%, @focus-color 1%) center/15000%;
  }
  &:active {
    background-color: @active-color;
    background-size: 100%;
    transition: background 0s;
  }
}

In SCSS:

  .ripple {
  background-position: center;
  transition: background 0.5s;
  &:hover {
    background: $focus-color radial-gradient(circle, transparent 1%, $focus-color 1%) center/15000%;
  }
  &:active {
    background-color: $active-color;
    background-size: 100%;
    transition: background 0s;
  }
}

Thanks for reading, feel free to let this post ripple through the web!