Heartbeat
A 40 px red circle pulsing in a double beat — first peak at 140 ms, second at 420 ms, ×1.3 at each beat, 300 ms rest — 60 BPM, pure CSS, zero JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →
3 usage examples



How it works
A single div.loader-heartbeat: 40 × 40 px, background: #ef4444 (red-500), border-radius: 50% for a perfect circle. The animation property is fully resolved in the sold CSS: 1s ease-in-out 0s infinite normal none running heartbeat — 1 s duration, no delay, infinite iterations. No children, no pseudo-elements, no JavaScript.
The @keyframes heartbeat animation has six steps describing a double beat (lub-dub): scale(1) at 0% → scale(1.3) at 14% (first beat peak at 140 ms) → scale(1) at 28% → scale(1.3) at 42% (second beat peak at 420 ms) → scale(1) at 70% → scale(1) at 100%. The rest pause between cycles lasts 300 ms (from 700 ms to 1,000 ms). At maximum scale, the circle measures 52 × 52 px. The ease-in-out easing softens each rise and fall. The second beat has a longer descent (280 ms vs. 140 ms for the first), matching a real cardiac rhythm.
Accessibility
- prefers-reduced-motion not handled: verified under Playwright, the animation keeps running (
animationPlayState: running) when the preference is enabled. No@media (prefers-reduced-motion)rule exists in the sold CSS. Add after the.loader-heartbeatblock:@media (prefers-reduced-motion: reduce) { .loader-heartbeat { animation: none; } }. The red circle stays visible — a readable static indicator with no motion. - As a loading indicator, this element has no semantic role in the sold code: there is no
role="status", noaria-busy, noaria-live, and no alternative text. Screen readers ignore it. To announce the loading state, wrap the circle in a<span role="status" aria-label="Loading">or add visually-hidden text. - Color alone: the effect relies solely on the red color and motion to signal activity. Make sure the context provides a text label or complementary icon (e.g. "Sending…") — especially if you change the color. On a dark background (
#0a0a0f),#ef4444measures 3.7:1, above the 3:1 threshold for graphic elements. - The sold code includes a
.demo-previewwrapper withbackground: #0a0a0f: this container is used only for the catalog demo. You only need.loader-heartbeatand the@keyframes heartbeatrule.
Browser compatibility
Pure CSS: transform: scale(), border-radius, animation. Zero JavaScript, zero dependencies.
Without CSS animations (very old browsers), the red circle remains visible and centered — a silent static indicator. border-radius: 50% is supported from Chrome 4 / Firefox 4 / Safari 5.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="loader-heartbeat"></div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Duration / BPM (CSS — animation-duration: 1s) |
1 s (60 BPM) | Change the value in the .loader-heartbeat rule. Keyframes stay proportional: 14% = first peak, 42% = second peak, 70% = end of second beat. 0.8s → 75 BPM (moderate effort); 1.4s → 43 BPM (rest, long wait); 0.5s → 120 BPM (alert). |
Beat amplitude (CSS — scale(1.3)) |
×1.3 (40 px → 52 px at each peak) | At keyframes 14% and 42%, replace 1.3. Values below 1.1 make the pulse nearly invisible; above 1.5 the circle may overflow in tight layouts — check overflow: hidden on the parent. |
Size (CSS — width/height: 40px) |
40 × 40 px | Resize directly. 18–20 px for a button; 60–80 px for a full-screen loading view. Consider lowering scale to 1.2 at small sizes to limit visual shift. |
Color (CSS — background: #ef4444) |
#ef4444 (red-500) | Replace the hex value. Red encodes vitality in this context; a neutral color (#6366f1) works for a generic loader. Check 3:1 contrast against your background. |
| Beat timing (CSS — keyframes 14%, 28%, 42%, 70%) | 14%→28% (1st) · 42%→70% (2nd) · rest 70%–100% | Move 14% and 42% closer (e.g. 10% / 35%) for a hurried rhythm; further apart (20% / 55%) for a slow, deep beat. Shortening the rest (70%→100% → 60%→100%) speeds up the cycle without changing the duration. |
FAQ
border-radius: 50%) pulsing in a double beat (lub-dub): first peak at 140 ms, second at 420 ms, 300 ms rest. For an actual heart, replace the div with a ❤ SVG or emoji in a span: the @keyframes heartbeat rule and animation shorthand apply as-is to any shape.spans that appear and disappear (scale 0 → 1 → 0) in a regular wave over 1.4 s. Heartbeat has a single red element that grows and shrinks with an asymmetric rhythm (two close beats then a 300 ms pause), no children, no JavaScript. The use case differs: dots suit any generic loading context; the cardiac pulse specifically signals activity in a medical, vital, or emotional register.@media (prefers-reduced-motion) rule (verified). Add after the .loader-heartbeat block: @media (prefers-reduced-motion: reduce) { .loader-heartbeat { animation: none; } }. The red circle stays visible — a readable static indicator with no motion. For a gentler fallback, substitute a slow fade: animation: 2s ease-in-out infinite alternate heartbeat-fade with @keyframes heartbeat-fade { from { opacity: .4 } to { opacity: 1 } }.