Tag: Material

How to Recreate the Ripple Effect of Material Design Buttons

When I first discovered Material Design, I was particularly inspired by its button component. It uses a ripple effect to give users feedback in a simple, elegant way.

How does this effect work? Material Design’s buttons don’t just sport a neat ripple animation, but the animation also changes position depending on where each button is clicked.

We can achieve the same result. We’ll start with a concise solution using ES6+ JavaScript, before looking at a few alternative approaches.


Our goal is to avoid any extraneous HTML markup. So we’ll go with the bare minimum:

<button>Find out more</button>

Styling the button

We’ll need to style a few elements of our ripple dynamically, using JavaScript. But everything else can be done in CSS. For our buttons, it’s only necessary to include two properties.

button {   position: relative;   overflow: hidden; }

Using position: relative allows us to use position: absolute on our ripple element, which we need to control its position. Meanwhile, overflow: hidden prevents the ripple from exceeding the button’s edges. Everything else is optional. But right now, our button is looking a bit old school. Here’s a more modern starting point:

/* Roboto is Material's default font */ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');  button {   position: relative;   overflow: hidden;   transition: background 400ms;   color: #fff;   background-color: #6200ee;   padding: 1rem 2rem;   font-family: 'Roboto', sans-serif;   font-size: 1.5rem;   outline: 0;   border: 0;   border-radius: 0.25rem;   box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3);   cursor: pointer; }

Styling the ripples

Later on, we’ll be using JavaScript to inject ripples into our HTML as spans with a .ripple class. But before turning to JavaScript, let’s define a style for those ripples in CSS so we have them at the ready:

span.ripple {   position: absolute; /* The absolute position we mentioned earlier */   border-radius: 50%;   transform: scale(0);   animation: ripple 600ms linear;   background-color: rgba(255, 255, 255, 0.7); }

To make our ripples circular, we’ve set the border-radius to 50%. And to ensure each ripple emerges from nothing, we’ve set the the default scale to 0. Right now, we won’t be able to see anything because we don’t yet have a value for the top, left, width, or height properties; we’ll soon be injecting these properties with JavaScript.

As for our CSS, the last thing we need to add is an end state for the animation:

@keyframes ripple {   to {     transform: scale(4);     opacity: 0;   } }

Notice that we’re not defining a starting state with the from keyword in the keyframes? We can omit from and CSS will construct the missing values based on those that apply to the animated element. This occurs if the relevant values are stated explicitly — as in transform: scale(0) — or if they’re the default, like opacity: 1.

Now for the JavaScript

Finally, we need JavaScript to dynamically set the position and size of our ripples. The size should be based on the size of the button, while the position should be based on both the position of the button and of the cursor.

We’ll start with an empty function that takes a click event as its argument:

function createRipple(event) {   // }

We’ll access our button by finding the currentTarget of the event.

const button = event.currentTarget;

Next, we’ll instantiate our span element, and calculate its diameter and radius based on the width and height of the button.

const circle = document.createElement("span"); const diameter = Math.max(button.clientWidth, button.clientHeight); const radius = diameter / 2;

We can now define the remaining properties we need for our ripples: the left, top, width and height.

circle.style.width = circle.style.height = `$ {diameter}px`; circle.style.left = `$ {event.clientX - (button.offsetLeft + radius)}px`; circle.style.top = `$ {event.clientY - (button.offsetTop + radius)}px`; circle.classList.add("ripple"); 

Before adding our span element to the DOM, it’s good practice to check for any existing ripples that might be leftover from previous clicks, and remove them before executing the next one.

const ripple = button.getElementsByClassName("ripple")[0];  if (ripple) {   ripple.remove(); }

As a final step, we append the span as a child to the button element so it is injected inside the button.


With our function complete, all that’s left is to call it. This could be done in a number of ways. If we want to add the ripple to every button on our page, we can use something like this:

const buttons = document.getElementsByTagName("button"); for (const button of buttons) {   button.addEventListener("click", createRipple); }

We now have a working ripple effect!

Taking it further

What if we want to go further and combine this effect with other changes to our button’s position or size? The ability to customize is, after all, one of the main advantages we have by choosing to recreate the effect ourselves. To test how easy it is to extend our function, I decided to add a “magnet” effect, which causes our button to move towards our cursor when the cursor’s within a certain area.

We need to rely on some of the same variables defined in the ripple function. Rather than repeating code unnecessarily, we should store them somewhere they’re accessible to both methods. But we should also keep the shared variables scoped to each individual button. One way to achieve this is by using classes, as in the example below:

Since the magnet effect needs to keep track of the cursor every time it moves, we no longer need to calculate the cursor position to create a ripple. Instead, we can rely on cursorX and cursorY.

Two important new variables are magneticPullX and magneticPullY. They control how strongly our magnet method pulls the button after the cursor. So, when we define the center of our ripple, we need to adjust for both the position of the new button (x and y) and the magnetic pull.

const offsetLeft = this.left + this.x * this.magneticPullX; const offsetTop = this.top + this.y * this.magneticPullY;

To apply these combined effects to all our buttons, we need to instantiate a new instance of the class for each one:

const buttons = document.getElementsByTagName("button"); for (const button of buttons) {   new Button(button); }

Other techniques

Of course, this is only one way to achieve a ripple effect. On CodePen, there are lots of examples that show different implementations. Below are some of my favourites.


If a user has disabled JavaScript, our ripple effect doesn’t have any fallbacks. But it’s possible to get close to the original effect with just CSS, using the :active pseudo-class to respond to clicks. The main limitation is that the ripple can only emerge from one spot — usually the center of the button — rather than responding to the position of our clicks. This example by Ben Szabo is particularly concise:

Pre-ES6 JavaScript

Leandro Parice’s demo is similar to our implementation but it’s compatible with earlier versions of JavaScript: 


This example use jQuery to achieve the ripple effect. If you already have jQuery as a dependency, it could help save you a few lines of code. 


Finally, one last example from me. Although it’s possible to use React features like state and refs to help create the ripple effect, these aren’t strictly necessary. The position and size of the ripple both need to be calculated for every click, so there’s no advantage to holding that information in state. Plus, we can access our button element from the click event, so we don’t need refs either.

This React example uses a createRipple function identical to that of this article’s first implementation. The main difference is that — as a method of the Button component — our function is scoped to that component. Also, the onClick event listener is now part of our JSX:

The post How to Recreate the Ripple Effect of Material Design Buttons appeared first on CSS-Tricks.

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


, , , , ,

Material Theming: Making Material Your Own!

The web is a beautiful, expressive medium that’s evolved over time as trends and technology have changed. Moments of delight and flair are what set companies apart from one another. At the same time, today’s top products rely on scalable, component-based design systems to efficiently develop a coherent brand. And it’s more important than ever to have accessibility and a solid user experience baked in from the start! But these two worlds — creative web design and systematic web design — don’t need to be at arms. The beautiful gooey center of the web design world is where we can find a way to meld creative web design in with our systems, and luckily, you can have both!

One of those design systems is called Material Design (the team I just joined at Google!). Material is built on years of research and best practices to give designers and developers access to creating beautiful, accessible UIs with the least amount of work possible. But in its initial launch, one thing was missing from the equation: the ability to express a brand’s personality in an easy-to-implement way. The team heard two key pieces of feedback:

  1. Material wasn’t stylistically flexible enough to meet the needs of all products, brands, and users.
  2. It wasn’t easy enough to apply and build brand experiences systematically across products.

And as of last year (specifically, I/O 2018), Material announced new theming capabilities! Theming allows developers and designers to benefit from all the parts of Material that make it a world-class design system — and make it their own! In other words: You can customize the look and feel of Material Components by applying global changes to your product’s color, shape, and typography.

Material Components are built for multiple systems like Android, iOS, and Flutter, and we’re going to focus on the web for this post (this is CSS-Tricks after all). Let’s start off with a base login template using Material Components for the web to see how easy implementing a theme atop this will be:

About theming

The current theming implementations allow for color, typography, and shape adjustments that trickle down to every component in your product. These three subsystems may not sound like many options, but together they bring a big impact to the design, and are a great springboard to jump off of with more design changes on a more granular level.


The first themable option is color. There’s a great color picker tool on Material.io for you to be able to see contrast and aid your color selection process. Since this is CSS-Tricks, let’s write some CSS (okay, more like SCSS), to get started with an example. In a few lines of code in our my-theme.scss file, we can entirely change the look and feel of this login screen:

We’ve set:

$ mdc-theme-primary: #26418f; $ mdc-theme-secondary: #d1c4e9; $ mdc-theme-background: #fdf6f9;

Even though we have not specified a $ mdc-theme-on-primary, the system knows to make this white now (using a Sass contrast function) to help us ensure accessible color contrast. We can also override the text color on the primary and secondary themes with $ mdc-theme-on-primary and $ mdc-theme-on-secondary. However, if no value is explicitly set, these will either be black or white based on the background. See the color picker for more information.


Material sets up a base typography scale, which can be customized with theming. You can adjust the typography for each individual headline level and apply it throughout your product in two ways:

  1. Using CSS classes to apply the styles. I.e. <h1 class="mdc-typography--headline1">
  2. Using mixins to extend the styles from a header component onto another (i.e. h1 {@include mdc-typography(headline1);}). You can see an example of this in our starter kit example on Glitch.

A quick way to change the typeface is by using Google Fonts — a really nice directory of free web fonts. We’re going to make this easy for ourselves and pick out a Google font called Josefin Sans. In order to use this in our project, we’ll need to import it, and then can set the base typography to use it:

@import url('https://fonts.googleapis.com/css?family=Josefin+Sans'); $ mdc-typography-font-family: unquote("Josefin Sans, sans-serif");

We use unquote here because the typography in Material is set in a map. We can specify more styles like so:

$ mdc-typography-styles-button: (  font-size: 14px,  font-weight: 600,  letter-spacing: 0.05em, );

Now our login page looks like this:


Shape is another way to be expressive with your code! What do I mean by shape? In Material, shape affects the corner radius of components, such as buttons, cards, and sheets. In product design, shape helps to define your brand. Is it angular and machine-like, or is it more rounded and organic? Changing shape trickles down through the rest of the system. Here are some examples of shape in Material:

The other cool thing about shape in Material Components is that you don’t have to have the same shape on each corner, or even the same component at different sizes. There are small, medium, and large components in Material, and you can apply different styles for these different component types, and you can mix it up and specify each corner, one-by-one, in a space-separated list of values just as you would with border-radius.

MDC Web does not currently support cut corners like the other platforms do since it is a non-trivial operation with current web standards. This is another reason I personally love Houdini and see a lot of impact for it in future CSS development!

In the example we’re working with, we have both large and small components. The large components are the text field inputs, and the small component is the “login” button. We can apply different shapes to these with:

$ mdc-shape-small-component-radius: 12px 4px; $ mdc-shape-large-component-radius: 8px;

And voilà! We have transformed our application in 12 lines of code.

Themes, themes, everywhere!

Now here’s the part where you get creative. There are so many ways to mix and match the elements of color, typography, and shape to really make your brand stand out. Here are four very distinct example apps that use color theory, typography, and shape to differentiate their products, each based on the baseline Material Components:

Themes IRL

Google itself customizes and extends upon the baseline Material Components with its own products. This is called the Google Material Theme, and it was defined by a number of product teams, including Gmail, Google News, Google Play, and Google Home:

But they are not the only company to use Material Design and its principles. A few companies have also extended Material Components in their own large-scale products.

Lyft, a ridesharing service, is just one example. They used an extended FAB component to highlight key actions, and designed it with a gradient to give it a unique feel that users were still familiar with. Lyft also leveraged Material’s elevation system in their own product.

Anchor, a free app that helps people record, distribute, and host podcasts, is another great example of making Material your own. They showcase their bold color palette, and integrate it with choice chips to delineate display options, a list of cards for content selection, and an extended FAB anchored to the bottom to remain persistently available for user actions.

Get started

Now you have everything you need to make own Material Theme with Material Components for the web. It’s easy to start building your web project with our Material Starter Kit, and exploring theming with the Build a Material theme Glitch project we’ve just released. Change around the variables in my-theme.scss to explore how they can customize the individual components. Then, you can open any template, paste those variables into the my-theme.scss, and start building.

TL;DR: If you thought that Material Design wasn’t for you in the past, it’s a great time to start exploring. Happy theming!

The post Material Theming: Making Material Your Own! appeared first on CSS-Tricks.


, ,