<!--Like Button -->
<button x-data="{
  state: 'Unliked',
  usedKeyboard: false,
  async updateState(to) {
      this.state = 'Saving'
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.state = to
  }
}"
:class="{
  'like unliked': state === 'Unliked',
  'like saving': state === 'Saving',
  'like liked': state === 'Liked',
  'focus:outline-none': !usedKeyboard
}"
@click="updateState(state === 'Unliked' ? 'Liked' : 'Unliked')" @keydown.window.tab="usedKeyboard = true">
  <span class="like-icon like-icon-state" aria-label="state" x-text="state" aria-live="polite">Unliked</span>
</button>


<!-- 👆
<button class="like [unliked/saving/liked]">
  <span class="like-icon like-icon-state" aria-label="[Unliked/Saving/Liked]" aria-live="polite">[Unliked/Saving/Liked]</span>
</button>
-->
/* Global Button Style for our like/unlike el */
.like {
  display: inline-block;
  width: 8rem; // <- Edit these value according to
  height: 8rem; // <- your ability to love unconditionally
  border-radius: 50%;
  background: rgba(20, 20, 20, 0.5);
  position: relative;
  cursor: pointer;
  transition: background-color 0.25s ease;
  overflow: hidden;
}

/* Global Button Hover Style for our like/unlike el */
.like:hover {
  background: #fff;
  transition: background-color 0.25s ease;
}

/* Liked/Unliked states of our like icon */
.liked .like-icon-state {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewbox='0 0 100 100'><path fill='%23de3618' d='M50,88.87 C76.67,70.46 90,53.9 90,39.17 C90,17.08 63.12,3.84 50,27.63 C38.875,3.85 10,17.08 10,39.17 C10,53.9 23.33,70.46 50,88.87 Z'/></svg>");
}

.unliked .like-icon-state {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewbox='0 0 100 100'><path fill='none' stroke='%23666' stroke-width='5' d='M50,88.87 C76.67,70.46 90,53.9 90,39.17 C90,17.08 63.12,3.84 50,27.63 C38.875,3.85 10,17.08 10,39.17 C10,53.9 23.33,70.46 50,88.87 Z'/></svg>");
}

.like span {
  text-indent:-1000px;
  fone-size:1px;
}

/* Global aspects of the like icon (holding our icon state view) */
.like-icon {
  background-size: contain;
  position: absolute;
  background-repeat: no-repeat;
  background-position: 50% 50%;
  top: 15%;
  right: 15%;
  bottom: 15%;
  left: 15%;
}

.liked .like-icon-state {
  transform: scale(0);
}

/* Set initial opacity and attach the like animation  */
.liked .like-icon-state {
  opacity: 0;
  animation: 1.2s like-animation both cubic-bezier(0.45, 0.05, 0.55, 0.95);
}

@keyframes like-animation {
  0% {
    transform: scale(0);
  }
  15% {
    transform: scale(1.25);
  }
  32% {
    transform: scale(0.95);
  }
  50% {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

/* Set initial opacity and attach the unlike animation  */
.unliked .like-icon-state {
  opacity: 0;
  animation: 1.2s unlike-animation both cubic-bezier(0.45, 0.05, 0.55, 0.95);
}

@keyframes unlike-animation {
  0% {
    transform: scale(0);
  }
  15% {
    transform: scale(1.1);
  }
  30% {
    transform: scale(0.98);
  }
  45% {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

/* Pop a small animation of the background on like */
.liked {
  background: #e2e2e2;
  animation: 1.2s liked-bg-animation both;
}

@keyframes liked-bg-animation {
  0% {
    transform: scale(0);
  }
  15% {
    transform: scale(1.1);
  }
  30% {
    transform: scale(0.95);
  }
  50% {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}


/* Pop a more discreet animation of the background on unlike */
.unliked {
  animation: 1.2s unliked-bg-animation both;
}


@keyframes unliked-bg-animation {
  0% {
    transform: scale(0);
  }
  10% {
    transform: scale(.70);
  }
  30% {
    transform: scale(0.95);
  }
  50% {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}


/* Pseudo el for our ring animation */
.like::before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.75);
  transform: scale(0);
}

/* Apply animation to the ring el */
.liked::before {
  animation: 1.2s bg-ring-animation both;
}

@keyframes bg-ring-animation {
  0% {
    transform: scale(0);
    opacity: 0;
  }
  10% {
    transform: scale(1.5);
    opacity: 0.25;
  }
  15% {
    transform: scale(2);
    opacity: 0.15;
  }
  45% {
    transform: scale(3.25);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 0;
  }
}

// Animation when loading 
// in htmx land the default class is .htmx-request
// instead of .saving

.saving::after {
  content: '';
  box-sizing: border-box;
  position: absolute;
  top: 20%;
  left: 20%;
  width: 60%;
  height: 60%;
  border: .15em solid rgba(100,100,100,.5);
  border-radius: 50%;
  border-right-color: transparent;
  border-top-color: transparent;
  animation: spin-animation 400ms infinite linear;
}


@keyframes spin-animation {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}



/* eof */
View Compiled
// 💡linking to alpinejs in the settings

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/1.5.0/tailwind.min.css

External JavaScript

  1. https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js