<!-- default button -->
<button>
  Button
</button>
<!-- secondary button, with border and transparent background -->
<button style="--background: none; --color: hsl(265, 100%, 48%); --border: hsl(265, 100%, 48%);">
  Button
</button>
<!-- tertiary button, no border, no background -->
<button style="--background: none; --color: hsl(265, 100%, 48%);">
  Button
</button>
<!-- icon button -->
<button>
  <svg viewBox="-50 -37.5 100 75" width="20" height="15">
    <g fill="currentColor" stroke="currentColor" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
      <path d="M -25 32.5 l 25 -65 25 65 -60 -42 70 0z" />
    </g>
  </svg>
  Button
</button>
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");

*,
*:before,
*:after {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

/* display the buttons in the center of the viewport, one above the other */
body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: hsl(0, 0%, 5%);
  background: hsl(0, 0%, 98%);
  font-family: "Roboto", sans-serif;
}
/* separate the buttons from the preceding elements */
body > * + * {
  margin-block-start: 2.5rem;
}
/* each button has the following properties
- top, left, size for the active state
- background, color, border for the color palette
*/
button {
  --top: 50%;
  --left: 50%;
  --size: 100%;
  --background: hsl(265, 100%, 50%);
  --color: hsl(265, 100%, 100%);
  /* :p */
  --border: none;
  color: var(--color);
  background: var(--background);
  border: 1px solid var(--border);

  font-family: inherit;
  padding: 0.6rem 1.25rem;
  font-size: 0.9rem;
  border-radius: 5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 1px;
  /* overflow and position matching the pseudo elements */
  overflow: hidden;
  position: relative;
  /* remove the default outline
! substituted with the pseudo elements
*/
  outline: none;
}
/* if existing, separate the inline icon from the text which follows */
button svg {
  width: 1em;
  height: auto;
  display: inline-block;
  margin-inline-end: 0.2rem;
}

/* with a pseudo element add a semitransparent layer with the same color of the text */
button:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: currentColor;
  border-radius: inherit;
  opacity: 0;
}
button:focus:before,
button:hover:before {
  opacity: 0.1;
}

/* with another pseudo element add a semitransparent layer clipped to be a circle */
button:after {
  content: "";
  position: absolute;
  top: var(--top);
  left: var(--left);
  transform: translate(-50%, -50%);
  width: var(--size);
  height: var(--size);
  background: currentColor;
  clip-path: circle(0%);
  opacity: 0.3;
  border-radius: inherit;
}
/*
! add the transition only as the active state is "enabled"
! add a negative delay for the clip to already start from a small circle
! add a delay for the opacity to follow the clip-path
*/
button:active:after {
  clip-path: circle(100%);
  opacity: 0;
  transition: clip-path 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53), opacity 0.4s ease-in-out;
  transition-delay: -0.1s, 0.5s;
}
// to have the pseudo element expand from where the cursor "hits" the button
// update the custom properties according to the precise coordinate
const buttons = document.querySelectorAll('button');

function handleClick(e) {
  const { layerX, layerY } = e;
  const { width, height } = this.getBoundingClientRect();

  this.style.setProperty('--top', `${(layerY / height) * 100}%`);
  this.style.setProperty('--left', `${(layerX / width) * 100}%`);

  // for the size consider the distance from the farthest angle
  const dx = layerX > width / 2 ? layerX : width - layerX;
  const dy = layerY > height / 2 ? layerY : height - layerY;
  const size = Math.sqrt(dx ** 2 + dy ** 2) * 2;
  this.style.setProperty('--size', `${size}px`);
}

buttons.forEach(button => {
  button.addEventListener('mousedown', handleClick);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.