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>
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;
}
Visual model:
circle + circle
\ /
rotated square
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);
}
}
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);
Good:
transform: rotate(45deg) scale(1.2);
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");
});
.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);
}
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;
}
}
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)