Day 18: Building a close button with ripple effect as a React component
Tags: webdev, react, button, ripple
Published: 2022-12-12 10:18:31

## TL;DR
This article goes through the steps to build a close button as a React component. 

You will also learn
1. HTML coding best practices for close buttons (Section 1)
2. How to style an SVG icon button (Section 3)
3. Best practices for styling the button focus state (Section 4)
4. How to display the ripple animation after clicking the button (Section 5)

The result is available as [a CodeSandbox demo](https://codesandbox.io/s/my-ideal-maps-close-button-epojyv?file=/src/components/CloseButton.js).

## Introduction

The web app I’m developing features a view of Google Maps embedded in full-screen. For the user to search a place on the map, to save a searched place, to see the detail of a saved place, and to change the user setting, the app needs a popup window to provide information and relevant buttons to the user. 

One of those buttons is the close button, that little cross icon at the top-right corner of a popup. Since it will be used for all these popups, it is best to build it as a reusable component. 

It turns out that making a close button component involves quite a bit of web dev techniques. In this article, I’d like to share what I have learned out of my experience of creating a close button as a React component.

## 1. HTML

When I start composing a React component, the first thing I do is to forget about React. Instead I solely focus on HTML. What HTML code do I want the React component to render? That’s the question I ask myself.

A little investigation into the best practices of coding HTML for the close button led me to discover two excellent articles on the subject: [Stanton (2020)](https://www.benjystanton.co.uk/blog/accessible-close-buttons/) and [Matuzovic (2020)](https://www.htmhell.dev/20-close-buttons/). Below is what I digest from them.

### 1.1 SVG Icon

Using the character _✕_ as an “icon” for the close button is not a good idea in terms of accessibility: for screen reader users, it does not mean “close something”. [Matuzovic (2020)](https://www.htmhell.dev/20-close-buttons/) writes:

> ✕ doesn’t represent close or crossed out, it’s the multiplication sign, like in 2 ✕ (times) 2. Don’t use it for close buttons.

Instead, the best approach is to use an SVG icon, which itself has no literal meaning. For my purpose, using SVG is also beneficial in terms of visual consistency: since I’m developing a web app that heavily features Google Maps (see [**Day 1 of this blog series**](https://dev.to/masakudamatsu/creating-saas-to-solve-my-own-problem-4gk9)), an SVG icon taken from Google‘s [Material Icons](https://fonts.google.com/icons?icon.set=Material+Icons) will achieve visual consistency in my web app design.

[Material Icons’s “Close” icon](https://fonts.google.com/icons?selected=Material%20Icons%20Outlined%3Aclose%3A) has the following SVG code:

```html
<svg
  xmlns="http://www.w3.org/2000/svg"
  height="24px"
  viewBox="0 0 24 24"
  width="24px"
  fill="#000000"
>
  <path d="M0 0h24v24H0V0z" fill="none" />
  <path
    d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
  />
</svg>
```

What I actually need is the `viewBox` attribute value and the second `<path>` element. 
- The `xmlns` attribute is unnecessary if the SVG code is embedded in HTML (see [MDN Web Docs on `<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)). 
- The `height` and `width` attributes are redundant when we use CSS to set the size (see [Bellamy-Royds 2015](https://css-tricks.com/scale-svg/#aa-the-height-and-width-attributes)). 
- So is the `fill` attribute (we can use CSS). 
- And I always wonder what the first `<path>` element is for, whenever I download Material Icons SVG images (please comment to this article if you have an idea).

So the first attempt to code HTML for the close button is something like this:

```html
<button type="button">
  <svg viewBox="0 0 24 24">
    <path
      d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
    />
  </svg>
</button>
```

### 1.2 Button label for screen reader users

However, the above HTML code would confuse screen reader users: the button has no label. To let them know what the button does, we need to add `aria-label`:

```html
<button aria-label="Close" type="button"> <!-- REVISED -->
  <svg aria-hidden="true" viewBox="0 0 24 24"> <!-- REVISED -->
    <path
      d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
    />
  </svg>
</button>
```

Here I apply the lesson learned on [**Day 16 of this blog series**](https://dev.to/masakudamatsu/day-16-icon-buttons-should-be-labelled-with-aria-label-551g): “Icon buttons should be labelled with `aria-label`” (see the link for detail). It also happens to be what [Stanton (2020)](https://www.benjystanton.co.uk/blog/accessible-close-buttons/) concluses as the best practice for close buttons.

You might wonder why the `<svg>` element is hidden for screen readers with `aria-hidden="true"`. Once the button is labeled with `aria-label`, screen reader users do not need to know whether there is an SVG icon on the button or not. So we can remove it for them.

### 1.3 “Close what?”

One additional consideration comes from the following passage from [Matuzovic (2020)](https://www.htmhell.dev/20-close-buttons/):

> Sometimes it makes sense to use more descriptive labels like “Close dialog”, “Close gallery”, or “Close ad”.

So we need some flexibility in the value of `aria-label`, to make it reusable in different contexts. That means the `aria-label` value needs to be a prop of the React component.

## 2. React component
To reuse the above HTML structure whenever we render a close button, we can create a Reaxt component. Here‘s how I go about it.

### 2.1 ARIA attributes as props

To allow `aria-label` to differ, I make it as a prop:

```js
export const CloseButton = ({ ariaLabel }) => {
  return (
    <button aria-label={ariaLabel} type="button">
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </button>
  );
};
```

To make it explicit that `aria-label` is always required, I add [`PropTypes`](https://reactjs.org/docs/typechecking-with-proptypes.html):

```js
import PropTypes from "prop-types"; // ADDED

export const CloseButton = ({ ariaLabel }) => {
  return (
    <button
      aria-controls={ariaControls}
      aria-expanded={ariaExpanded}
      aria-label={ariaLabel}
      type="button"
    >
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </button>
  );
};

// ADDED FROM HERE
CloseButton.propTypes = {
  ariaLabel: PropTypes.string.isRequired,
};
// ADDED UNTIL HERE
```

So, in case I forget giving the `ariaLabel` prop to the `CloseButton` component, React will complain in the JavaScript console. (Which indeed happened when I was creating a demo for this article with CodeSandbox.)

Maybe it’s time for me to start using TypeScript, though.

### 2.2 Click handler

Of course, we need to attach a click event handler to the `<button>` element so that the pressing of it will close something.

For this purpose, I add `handleClick` prop:

```js
import PropTypes from "prop-types";

export const CloseButton = ({
  ariaLabel,
  handleClick, // ADDED
}) => {
  return (
    <button
      aria-label={ariaLabel}
      onClick={handleClick} // ADDED
      type="button"
    >
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </button>
  );
};

CloseButton.propTypes = {
  ariaLabel: PropTypes.string.isRequired,
  clickHandler: PropTypes.string.isRequired, // ADDED
};
```

Again, I add the prop type so React will complain if I forget adding the `clickHandler` prop.

Now, let’s move on to style the close button.

## 3. CSS

### 3.1 Using Styled Components

To style React components, I always use [Styled Components](https://styled-components.com):

```js
import PropTypes from 'prop-types';

import { Button } from '../styled-components/Button'; // ADDED

export const CloseButton = ({
  ariaLabel,
  handleClick
}) => {
  return (
    <Button // REVISED
      aria-label={ariaLabel}
      onClick={handleClick}
      type="button"
    >
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </Button> {/* REVISED */}
  );
};

CloseButton.propTypes = {
  ariaLabel: PropTypes.string.isRequired,
  clickHandler: PropTypes.string.isRequired,
};
```

where the `Button` styled component is defined in a separate file:

```js
// styled-components/Button.js

import styled from "styled-components";

export const Button = styled.button`
  /* CSS declarations to be inserted */
`;
```

In this file, let’s add CSS declarations to style the close button step by step.

### 3.2 Reset the default button style

The `button` element has a particular set of default style. I want to reset it first:

```js
// styled-components/Button.js

import styled from "styled-components";

// ADDED FROM HERE
const resetStyle = `
  background-color: rgba(255, 255, 255, 0);
  border: none;
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle} /* ADDED */
`;
```

The kind of the close button that I’m after is minimally styled, just a cross mark icon without borders or solid background color.

For transparency, I always use the transparent white (i.e., `rgba(255, 255, 255, 0)`) rather than the named color keyword of `transparent` ever since I learned about cross-browser inconsistency: Safari interprets `transparent` as the transparent _black_ (see [Coyier 2017](https://css-tricks.com/thing-know-gradients-transparent-black/)).

And I use a variable `resetStyle` to define a set of CSS declarations for code readability. This is what I like about CSS-in-JS.

### 3.3 Set clickable area

Next, let‘s shape the close button. 

Although there is no border, when the keyboard user focuses it with the Tab key, I want it to show a circular focus ring, rather than a square one. The `border-radius: 50%;` does the job.

For the size of the button, Google recommends the 48x48 pixel area as a minimum touch target size, because:

> “The 48x48 pixel area corresponds to around 9mm, which is about the size of a person’s finger pad area.” ([Gash et al. 2020](https://web.dev/accessible-tap-targets/))

So the diameter of the button is set to be 48px.

Therefore, we have:

```js
// styled-components/Button.js

import styled from 'styled-components';

...

// ADDED FROM HERE
const setClickableArea = `
  --minimum-target-size: 48px;
  border-radius: 50%;
  height: var(--minimum-target-size);
  width: var(--minimum-target-size);
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea} /* ADDED */
`;
```

Using a CSS variable name (`--minimum-target-size`), I can embed the logic behind the numbers into the CSS code.

### 3.4 Center-align the icon

Now we need to position the SVG icon inside the clickable area. I want it to be center-aligned both vertically and horizontally. So I use flexbox:

```js
// styled-components/Button.js

import styled from 'styled-components';

...

// ADDED FROM HERE
const centerAlignIcon = `
  align-items: center;
  display: flex;
  justify-content: center;
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${centerAlignIcon} /* ADDED */
`;
```

### 3.5 Style the icon

Now let’s style the icon:

```js
// styled-components/Button.js

import styled from 'styled-components';

...

// ADDED FROM HERE
const styleIcon = `
  & svg {
    fill: var(--button-label-color-default);
    height: calc(var(--minimum-target-size) * 0.75);
    width: calc(var(--minimum-target-size) * 0.75);
  }
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${centerAlignIcon}
  ${styleIcon} /* ADDED */
`;
```

To specify the color of an SVG icon, we need the `fill` property. I use a CSS variable so it will switch between light and dark modes (see [Dodds 2020](https://epicreact.dev/css-variables/) for how).

The SVG icon’s `height` and `width` are set to be three-quarters of the minimum target size of 48px, which makes the close button look decent when the focus ring appears around it. Rather than hard-coding 36px, using the `calc()` CSS function and the CSS variable of `--minimum-target-size` makes clear the logic behind the choice of 36px.

That’s all for the default style.

Notice that I don’t include any CSS code for positioning the close button at the top-right corner of a dialog. That’s the job for the CSS code of a dialog element (`<div role="dialog>`). We will see an example of it in the future article of this blog series.

## 4. Focus state style

### 4.1 Consistency with cloud buttons
On [**Day 7 of this blog series**](https://dev.to/masakudamatsu/day-7-making-buttons-look-like-clouds-for-embedded-google-maps-33po) (see Section 8), I styled the focus ring for cloud-shaped buttons in a shade of blue `rgb(69,159,189)` and made it glow with `drop-shadow(0 0 5px rgb(69,159,189))`. Also, I made the button label's color change to `rgb(3, 3, 3)`. 

To achieve visual consistency, I want the close button to have the same focus ring. So the first attempt is something like this:
```js
// styled-components/Button.js
import styled from 'styled-components';

...

// ADDED FROM HERE
const glowFocusRing = `
  border: 1px solid rgb(69, 159, 189);
  box-shadow: 0 0 5px rgb(69, 159, 189);
`;
const darkenButtonLabel = `
  fill: rgb(3, 3, 3);
`;
const supportHighContrastMode = `
  outline: 1px solid transparent;
`
const styleFocusState = `
  &:focus {
    ${glowFocusRing}
    ${supportHighContrastMode}
  }
  &:focus svg {
    ${darkenButtonLabel}
  }
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${centerAlignIcon}
  ${styleIcon}
  ${styleFocusState} /* ADDED */
`;
```

The above code gives us a focus ring like this:

![The cross icon surrounded with a glowing sky blue ring](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kcf1b3g0xdhcutrrm8w.png)

### 4.2 Fallback for Windows High Contrast Mode

The above CSS code includes the declaration of `outline: 1px solid transparent`. This is for Windows High Contrast Mode, which removes the color of border and box shadow and instead renders any outline in solid white (even if it’s transparent). It’s becoming the norm (as far as I see) to include a transparent outline when the custom focus style involves border and box shadow. See [Higley (2020)](https://sarahmhigley.com/writing/whcm-quick-tips/) for detail.

An added bonus is that it also removes the default focus ring by overriding the `outline` property.

### 4.3 Remove focus style for touch device users and mouse users
The focus state style for a button is unnecessary for smartphone/tablet users and mouse users. We only need to style the focus state for keyboard users. 

However, for all the browsers except Safari, the `:focus` pseudo selector gets activated when the button is clicked (as pointed out by [Underhill 2021](https://www.tempertemper.net/blog/refining-focus-styles-with-focus-visible)). So there will be a flash of the focus style when touch device users tap the button (or when mouse users click the button). That’s not desirable.

A solution is the `:focus-visible` pseudo selector which gets activated only when the button is focused with keyboard strokes. [Underhill (2021)](https://www.tempertemper.net/blog/refining-focus-styles-with-focus-visible) provides an excellent overview of `:focus-visible`.

But there is an issue of handling those “legacy” browsers (including [the versions of Safari released before March 2022](https://caniuse.com/css-focus-visible)) that do not support `:focus-visible`. Consequently, we also need to use the `:focus` pseudo selector so that keyboard users with old devices won’t be ignored.

[Lauke (2018)](https://www.tpgi.com/focus-visible-and-backwards-compatibility/) proposes to define the focus state style with `:focus` for legacy browsers and then to remove the focus state style with the following pseudo selector:

```css
:focus:not(:focus-visible)
```

which refers to the clicking of a button by smartphone, tablet and mouse users.

Following this technique, I revise the previous code for styling the focus state as follows:

```js
// styled-components/Button.js
import styled from 'styled-components';

...

const glowFocusRing = `
  border: 1px solid rgb(69, 159, 189);
  box-shadow: 0 0 5px rgb(69, 159, 189);
`;
const darkenButtonLabel = `
  fill: rgb(3, 3, 3);
`;
const supportHighContrastMode = `
  outline: 1px solid transparent;
`;

// ADDED FROM HERE
const removeFocusRing = `
  border: none;
  box-shadow: none;
`;
const resetButtonLabelColor = `
  fill: rgb(90, 90, 90);
`;
// ADDED UNTIL HERE

const styleFocusState = `
  &:focus {
    ${glowFocusRing}
    ${supportHighContrastMode}
  }
  &:focus svg {
    ${darkenButtonLabel}
  }

  /* ADDED FROM HERE */
  &:focus:not(:focus-visible) {
    ${removeFocusRing}
  }
  &:focus:not(:focus-visible) svg {
    ${resetButtonLabelColor}
  }
  /* ADDED UNTIL HERE */
`;

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${centerAlignIcon}
  ${styleIcon}
  ${styleFocusState}
`;
```
where `rgb(90, 90, 90)` is the default color for button icons.

## 5. Active state style

The active state (or what Material Design calls the [“pressed” state](https://material.io/design/interaction/states.html#pressed)) is critical to tell the user whether the button is pressed or not. If the button doesn‘t visually respond to the user’s action, the user might think the app is not working, especially when the network connection is slow or the user’s device is slow (like my iPad Mini 2 bought in 2015).

For some reason, many web designers and web developers are not keen on styling the button active state. [Lorenz (2019)](https://dev.to/webdeasy/top-20-css-buttons-animations-f41) showcases the CodePen demos of “Top 50 CSS Buttons”. When I click these buttons, very few of them clealry indicate that the button is indeed pressed.

### 5.1 Ripple effect

Today the most popular active state style is probably the [ripple effect](https://material-components.github.io/material-components-web-catalog/#/component/ripple), popularized by Google with its Material Design.

And I follow this practice because, due to the popularity, the user can immediately tell what ripple animation means. Strictly speaking, it is not fully consistent with the design concept of my web app (see [**Day 2 of this blog series**](https://dev.to/masakudamatsu/day-2-finding-design-concept-to-drive-ui-design-process-49g3)). For creating the minimum viable product, however, the ripple effect is good enough, and relatively easy to implement.

### 5.2 Adding the ripple to the DOM
[Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/) provides [a React code snippet for the ripple effect](https://codepen.io/BretCameron/pen/ZEWJKbN). Adapting this snippet, I first write down a function called `createRipple`:

```js
// /utils/createRipple.js

export function createRipple(event) {
  const button = event.currentTarget;

  const diameter = Math.max(button.clientWidth, button.clientHeight);
  const radius = diameter / 2;

  const circle = document.createElement('span');

  circle.style.width = circle.style.height = `${diameter}px`;
  circle.style.left = `${event.offsetX - radius}px`;
  circle.style.top = `${event.offsetY - radius}px`;

  circle.classList.add('ripple');

  circle.addEventListener('animationend', () => button.removeChild(circle));

  button.appendChild(circle);
}
```

The core idea is to create a temporary `<span>` element as a child of the `<button>` element, to shape it as a circle, and to set its `left` and `top` CSS properties to be dependent on where the user clicks on the button (`event.offsetX` and `event.offsetY`). For more detail, see [Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/).

Note that the above code is revised from the original snippet by [Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/). I have incorporated a couple of comments to the article:
- [Gonzalez (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/#comment-1764066) for the use of `offsetX` and `offsetY`
- [Jonas (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/#comment-1764053) for removing the `<span>` element once animation is over.

### 5.3 Style the ripple
The above code attaches a class `ripple` to the `<span>` element. This class is used to style the ripple:

```js
// /styled-components/Button.js
import styled, {css, keyframes} from 'styled-components'; // REVISED

...

// ADDED FROM HERE
const ripple = keyframes`
  to {
      transform: scale(4);
      opacity: 0;
    }
`;
const styleActiveState = css`
  overflow: hidden;
  position: relative;
  & .ripple {
    animation: ${ripple} 600ms linear;
    background-color: var(--ripple-color);
    border-radius: 50%;
    position: absolute;
    transform: scale(0);
  }
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${centerAlignIcon}
  ${styleIcon}
  ${styleFocusState}
  ${styleActiveState} /* ADDED */
`;
```
At the top of the code, I import the `css` and `keyframes` helper functions, which is necessary to use CSS animation with Styled Components. The `keyframes` is for defining animation, and the `css` is required whenever CSS declarations include the animation keyframe (see [Styled Components Docs](https://styled-components.com/docs/basics#animations) for detail).

The animation defined with `keyframes` involves `transform` and `opacity` properties. The `<span>` element is animated to scale from zero to four with `transform: scale()`. It’s not clear why four. But I follow [Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/), who must have reached this magnic number by trial and error.

And `opacity` is animated from 1 (default) to 0, imitating the vanishing water ripples.

Animation duration is 600ms, which is another magic number from [Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/). Animation easing is `linear`: water ripples move at a constant speed according to [this Reddit discussion](https://www.reddit.com/r/askscience/comments/3yttne/what_determines_the_speed_at_which_water_ripples/).

The color of the ripple is set with `--ripple-color`, which is defined (elsewhere in the code base) as `rgba(3, 3, 3, 0.33)` for light mode and `rgba(255, 255, 255, 0.4)` for dark mode. According to the [Material Design](https://material.io/design/interaction/states.html#pressed) guideline, the (initial) opacity of the ripple should be higher the darker the background.

Finally, the `<button>` element itself needs to be styled with

```css
overflow: hidden;
position: relative;
```

so that the `<span>` element’s position is contained inside the button and that the ripple disappears when it hits the edges of the button.

### 5.4 Create the ripple on the click event

Then execute the `createRipple` function when the user clicks the button (but before executing the close button’s functionality given by the `handleClick` prop):

```js
import PropTypes from 'prop-types';

import { Button } from '../styled-components/Button';

import { createRipple } from '../utils/createRipple'; // ADDED

export const CloseButton = ({
  ariaLabel,
  handleClick,
}) => {
  // ADDED FROM HERE
  const clickHandler = (event) => {
    createRipple(event);
    handleClick();
  };
  // ADDED UNTIL HERE

  return (
    <Button
      aria-label={ariaLabel}
      onClick={clickHandler} // REVISED
      type="button"
    >
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </Button>
  );
};

CloseButton.propTypes = {
  ariaLabel: PropTypes.string.isRequired,
  clickHandler: PropTypes.string.isRequired,
};
```

The snippet by [Cameron (2020)](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/) ends here. But I realize that there is one thing missing: handling the button click with a keyboard.

### 5.5 Handle keyboard users
The above code won’t create the ripple effect when keyboard users press the button with the Enter key. 

Let’s assume that the button is pressed at its center in this case. So the `createRipple` function is revised as follows:

```js
// /utils/createRipple.js

export function createRipple(event) {
  const button = event.currentTarget;

  const diameter = Math.max(button.clientWidth, button.clientHeight);
  const radius = diameter / 2;

  const circle = document.createElement('span');

  circle.style.width = circle.style.height = `${diameter}px`;

  // REVISED FROM HERE
  if (event.key === 'Enter') {
    circle.style.left = 0;
    circle.style.top = 0;
  } else {
    circle.style.left = `${event.offsetX - radius}px`;
    circle.style.top = `${event.offsetY - radius}px`;
  } 
  // REVISED UNTIL HERE

  circle.classList.add('ripple');

  circle.addEventListener('animationend', () => button.removeChild(circle));

  button.appendChild(circle);
}
```

Also the `CloseButton` component needs to be revised as follows:

```js
import PropTypes from 'prop-types';

import { Button } from '../styled-components/Button';

import { createRipple } from '../utils/createRipple';

export const CloseButton = ({
  ariaLabel,
  handleClick,
}) => {
  const clickHandler = (event) => {
    createRipple(event);
    handleClick();
  };

  // ADDED FROM HERE
  const keydownHandler = (event) => {
    if (event.key === 'Enter') {
      event.preventDefault(); // otherwise click event will be fired as well
      createRipple(event);
      handleClick();
    }
  };
  // ADDED UNTIL HERE

  return (
    <Button
      aria-label={ariaLabel}
      onClick={clickHandler}
      onKeyDown={keydownHandler} // ADDED
      type="button"
    >
      <svg aria-hidden="true" viewBox="0 0 24 24">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
      </svg>
    </Button>
  );
};

CloseButton.propTypes = {
  ariaLabel: PropTypes.string.isRequired,
  clickHandler: PropTypes.string.isRequired,
};
```

## Demo
Here’s a [CodeSandbox demo of what the code in this article achieves](https://codesandbox.io/s/my-ideal-maps-close-button-epojyv?file=/src/components/CloseButton.js). Try to click the close button on the top right. It won’t close anything, but it does give you the UI feedback of the ripple effect!

## References

Bellamy-Royds, Amelia (2015) “[How to Scale SVG](https://css-tricks.com/scale-svg/)”, _CSS-Tricks_, Jan 6, 2015.

Cameron, Bret (2020) “[How to Recreate the Ripple Effect of Material Design Buttons](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/)”, _CSS-Tricks_, Oct 12, 2020.

Coyier, Chris (2017) “[A Thing To Know about Gradients and ‘Transparent Black’](https://css-tricks.com/thing-know-gradients-transparent-black/)”, _CSS-Tricks_, Jan 10, 2017.

Dodds, Kent C. (2020) “[Use CSS Variables instead of React Context](https://epicreact.dev/css-variables/)”, _Epic React_, Oct 2020.

Gash, Dave, Meggin Kearney, Rachel Andrew, Rob Dodson, and Patrick H. Lauke (2020) “[Accessible tap targets](https://web.dev/accessible-tap-targets/)”, _web.dev_, Mar 31, 2020.

Gonzalez, Angelo (2020) “[Awesome work! For anyone trying to apply this to absolute positioned elements...](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/#comment-1764066)”, comment to _CSS-Tricks_, Oct 14, 2020.

Higley, Sarah (2020) “[Quick Tips for High Contrast Mode](https://sarahmhigley.com/writing/whcm-quick-tips/)”, _sarahmhigley.com_, May 26, 2020. 

Jonas (2020) “[Thanks for this nice article! One thing I did differently is...](https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/#comment-1764053)”, comment to _CSS-Tricks_, Oct 14, 2020.

Lauke, Patrick H. (2018) “[:focus-visible and backwards compatibility](https://www.tpgi.com/focus-visible-and-backwards-compatibility/)”, _TPGi_, Mar 23, 2018.

Lorenz (2019) “[Top 50 CSS Buttons (+ animations)](https://dev.to/webdeasy/top-20-css-buttons-animations-f41)”, _Dev Community_, Jun 9, 2019.

Matuzovic, Manuel (2020) “[#20 HTMHell special: close buttons](https://www.htmhell.dev/20-close-buttons/)”, _HTMHell_, May 23, 2020.

Stanton, Benjy (2020) “[Accessible close buttons](https://www.benjystanton.co.uk/blog/accessible-close-buttons/)”, _benjystanton.co.uk_, Apr 30, 2020.

Underhill, Martin (2021) “[Refining focus styles with focus-visible](https://www.tempertemper.net/blog/refining-focus-styles-with-focus-visible)”, _tempertemper_, May 25, 2021.