DEV Community

Cover image for CSS Heart Animation: Small Demo, Real Animation Lessons
Mark Yu
Mark Yu

Posted on • Edited on

CSS Heart Animation: Small Demo, Real Animation Lessons

Small CSS animations are underrated.

They look like toy demos, but they teach the same things you need for real UI work: transforms, timing, layout, pseudo-elements, and not accidentally triggering expensive repaint work.

Let’s build a heart animation with mostly CSS.

The HTML

Keep the markup boring:

<main class="stage">
  <button class="heart-button" aria-label="Like">
    <span class="heart"></span>
  </button>
</main>
Enter fullscreen mode Exit fullscreen mode

The button gives us keyboard accessibility. The span handles the shape.

The CSS Shape

A heart is basically a rotated square with two circles attached.

.stage {
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: #111827;
}

.heart-button {
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 48px;
}

.heart {
  position: relative;
  display: block;
  width: 80px;
  height: 80px;
  background: #ef4444;
  transform: rotate(45deg);
}

.heart::before,
.heart::after {
  content: "";
  position: absolute;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: #ef4444;
}

.heart::before {
  left: -40px;
}

.heart::after {
  top: -40px;
}
Enter fullscreen mode Exit fullscreen mode

Visual model:

circle + circle
   \     /
 rotated square
Enter fullscreen mode Exit fullscreen mode

That is the whole trick.

Add the Pulse

Use transform, not layout properties.

.heart-button:hover .heart,
.heart-button:focus-visible .heart {
  animation: pulse 700ms ease-in-out infinite;
}

@keyframes pulse {
  0% {
    transform: rotate(45deg) scale(1);
  }
  45% {
    transform: rotate(45deg) scale(1.18);
  }
  100% {
    transform: rotate(45deg) scale(1);
  }
}
Enter fullscreen mode Exit fullscreen mode

The common mistake is forgetting the rotation inside the keyframes. If you animate only scale(), the heart can snap back because transform gets replaced.

Bad:

transform: scale(1.2);
Enter fullscreen mode Exit fullscreen mode

Good:

transform: rotate(45deg) scale(1.2);
Enter fullscreen mode Exit fullscreen mode

Add a Click State With JavaScript

CSS hover is nice, but click feedback feels better.

const button = document.querySelector(".heart-button");

button.addEventListener("click", () => {
  button.classList.toggle("is-liked");
});
Enter fullscreen mode Exit fullscreen mode
.heart-button.is-liked .heart,
.heart-button.is-liked .heart::before,
.heart-button.is-liked .heart::after {
  background: #fb7185;
}

.heart-button.is-liked .heart {
  box-shadow: 0 0 48px rgba(251, 113, 133, 0.55);
}
Enter fullscreen mode Exit fullscreen mode

Respect Reduced Motion

This is a small demo, but accessibility still counts.

@media (prefers-reduced-motion: reduce) {
  .heart-button:hover .heart,
  .heart-button:focus-visible .heart {
    animation: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

I would add this even for playful UI. Not every user wants pulsing motion.

What This Demo Teaches

Technique Real UI use
::before / ::after icons, badges, decorative layers
transform cheap animation
@keyframes reusable motion
focus-visible keyboard-friendly interaction
reduced motion accessibility polish

Final Thought

This is not just a heart animation. It is a tiny lab for learning how browser motion behaves.

What small CSS animation taught you more than you expected?

Top comments (0)