30 CSS Keyframe Animations 24 / 30

CSS Breathing Animation Meditation

Six calming CSS breathing animations: classic expanding circle, box-breathing dot tracer, ripple rings, lotus bloom petals, SVG wave breath and stroke-dashoffset timer ring.

Pure CSS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<div class="kf-24">
  <h2>CSS Breathing & Meditation Animations</h2>
  <div class="grid">

    <div class="breathe-card">
      <div class="breathe-circle"></div>
      <div class="breathe-label">breathe in · hold · breathe out</div>
      <label>Classic Breathing</label>
    </div>

    <div class="breathe-card">
      <div class="box-wrap">
        <div class="box-bg"></div>
        <div class="box-glow"></div>
        <div class="box-dot"></div>
      </div>
      <label>Box Breathing</label>
    </div>

    <div class="breathe-card">
      <div class="ripple-breath">
        <div class="ripple-ring"></div>
        <div class="ripple-ring"></div>
        <div class="ripple-ring"></div>
        <div class="ripple-core"></div>
      </div>
      <label>Ripple Breath</label>
    </div>

    <div class="breathe-card">
      <div class="lotus-wrap">
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="petal"></div>
        <div class="lotus-core"></div>
      </div>
      <label>Lotus Bloom</label>
    </div>

    <div class="breathe-card">
      <div class="wave-breath">
        <svg viewBox="0 0 200 80">
          <path d="M 0,40 Q 50,40 100,40 Q 150,40 200,40"/>
        </svg>
      </div>
      <label>Wave Breath</label>
    </div>

    <div class="breathe-card">
      <div class="timer-breath">
        <svg viewBox="0 0 120 120">
          <defs>
            <linearGradient id="kf24grad" x1="0%" y1="0%" x2="100%" y2="100%">
              <stop offset="0%" stop-color="#7c6af7"/>
              <stop offset="100%" stop-color="#00d4ff"/>
            </linearGradient>
          </defs>
          <circle class="timer-track" cx="60" cy="60" r="52"/>
          <circle class="timer-prog" cx="60" cy="60" r="52"/>
        </svg>
        <div class="timer-text">◯</div>
      </div>
      <label>Breath Timer</label>
    </div>

  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap');
.kf-24 *, .kf-24 *::before, .kf-24 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.kf-24 {
  font-family: 'Inter', sans-serif;
  background: #06080f;
  color: #fff;
  padding: 48px 24px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
}
.kf-24 h2 {
  font-size: 1rem;
  color: #444;
  letter-spacing: 3px;
  text-transform: uppercase;
  font-weight: 300;
}
.kf-24 .grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 28px;
  width: 100%;
  max-width: 960px;
}

/* 1: Classic breathing circle */
.kf-24 .breathe-card {
  background: #0a0c18;
  border-radius: 20px;
  padding: 40px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  border: 1px solid #151830;
}
.kf-24 .breathe-card label {
  font-size: 0.75rem;
  color: #444;
  letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 300;
}
.kf-24 .breathe-circle {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(100,160,255,0.8) 0%, rgba(80,120,220,0.4) 50%, rgba(50,80,180,0.1) 100%);
  box-shadow:
    0 0 0 0 rgba(100,160,255,0.4),
    0 0 40px rgba(80,140,255,0.3);
  animation: kf-24-breathe 8s ease-in-out infinite;
}
@keyframes kf-24-breathe {
  0%   { transform: scale(0.85); box-shadow: 0 0 0 0 rgba(100,160,255,0.6), 0 0 20px rgba(80,140,255,0.2); }
  35%  { transform: scale(1.25); box-shadow: 0 0 0 20px rgba(100,160,255,0), 0 0 60px rgba(80,140,255,0.5); }
  65%  { transform: scale(1.25); box-shadow: 0 0 0 0 rgba(100,160,255,0), 0 0 60px rgba(80,140,255,0.5); }
  100% { transform: scale(0.85); box-shadow: 0 0 0 0 rgba(100,160,255,0.6), 0 0 20px rgba(80,140,255,0.2); }
}
.kf-24 .breathe-label {
  font-size: 1rem;
  font-weight: 300;
  color: rgba(140,180,255,0.8);
  letter-spacing: 2px;
  animation: kf-24-breathe-label 8s ease-in-out infinite;
  text-align: center;
}
@keyframes kf-24-breathe-label {
  0%, 5%   { content: 'Breathe in'; opacity: 0.5; }
  10%      { opacity: 1; }
  30%, 40% { opacity: 1; }
  45%      { opacity: 0.3; }
  60%      { opacity: 0.8; }
  80%, 95% { opacity: 0.8; }
  100%     { opacity: 0.5; }
}

/* 2: Box breathing */
.kf-24 .box-wrap {
  position: relative;
  width: 140px;
  height: 140px;
}
.kf-24 .box-bg {
  width: 100%;
  height: 100%;
  border-radius: 20px;
  background: #0f1020;
  border: 1px solid #1e2040;
}
.kf-24 .box-dot {
  position: absolute;
  width: 16px;
  height: 16px;
  background: #7c6af7;
  border-radius: 50%;
  top: -8px;
  left: -8px;
  box-shadow: 0 0 16px rgba(124,106,247,0.8);
  animation: kf-24-boxbreath 16s linear infinite;
}
@keyframes kf-24-boxbreath {
  0%   { top: -8px; left: -8px; }
  25%  { top: -8px; left: calc(100% - 8px); }
  50%  { top: calc(100% - 8px); left: calc(100% - 8px); }
  75%  { top: calc(100% - 8px); left: -8px; }
  100% { top: -8px; left: -8px; }
}
.kf-24 .box-glow {
  position: absolute;
  inset: 4px;
  border-radius: 16px;
  border: 2px solid transparent;
  animation: kf-24-boxglow 16s linear infinite;
}
@keyframes kf-24-boxglow {
  0%, 100% { border-color: rgba(124,106,247,0.3); }
  12.5%, 37.5%, 62.5%, 87.5% { border-color: rgba(124,106,247,0.8); }
}

/* 3: Ripple breath */
.kf-24 .ripple-breath {
  position: relative;
  width: 120px;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.kf-24 .ripple-ring {
  position: absolute;
  border-radius: 50%;
  border: 2px solid rgba(0, 212, 106, 0.6);
  animation: kf-24-ripplering 4s ease-out infinite;
}
.kf-24 .ripple-ring:nth-child(1) { width: 40px; height: 40px; animation-delay: 0s; }
.kf-24 .ripple-ring:nth-child(2) { width: 40px; height: 40px; animation-delay: 1s; }
.kf-24 .ripple-ring:nth-child(3) { width: 40px; height: 40px; animation-delay: 2s; }
.kf-24 .ripple-core {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: radial-gradient(circle, #00d46a, #00a050);
  z-index: 2;
  animation: kf-24-corePulse 4s ease-in-out infinite;
}
@keyframes kf-24-ripplering {
  0%   { width: 40px; height: 40px; opacity: 0.8; border-color: rgba(0,212,106,0.8); }
  100% { width: 130px; height: 130px; opacity: 0; border-color: rgba(0,212,106,0); }
}
@keyframes kf-24-corePulse {
  0%, 100% { transform: scale(0.9); }
  50%       { transform: scale(1.1); }
}

/* 4: Petals / lotus breath */
.kf-24 .lotus-wrap {
  position: relative;
  width: 140px;
  height: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.kf-24 .petal {
  position: absolute;
  width: 40px;
  height: 60px;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  transform-origin: bottom center;
  animation: kf-24-petal 6s ease-in-out infinite;
}
.kf-24 .petal:nth-child(1) { background: rgba(233,30,140,0.5); transform: rotate(0deg)   translateY(-20px); animation-delay: 0s; }
.kf-24 .petal:nth-child(2) { background: rgba(124,106,247,0.5); transform: rotate(45deg)  translateY(-20px); animation-delay: 0.15s; }
.kf-24 .petal:nth-child(3) { background: rgba(0,212,255,0.5);   transform: rotate(90deg)  translateY(-20px); animation-delay: 0.3s; }
.kf-24 .petal:nth-child(4) { background: rgba(0,212,106,0.5);   transform: rotate(135deg) translateY(-20px); animation-delay: 0.45s; }
.kf-24 .petal:nth-child(5) { background: rgba(247,201,72,0.5);  transform: rotate(180deg) translateY(-20px); animation-delay: 0.6s; }
.kf-24 .petal:nth-child(6) { background: rgba(255,107,53,0.5);  transform: rotate(225deg) translateY(-20px); animation-delay: 0.75s; }
.kf-24 .petal:nth-child(7) { background: rgba(233,30,140,0.4);  transform: rotate(270deg) translateY(-20px); animation-delay: 0.9s; }
.kf-24 .petal:nth-child(8) { background: rgba(124,106,247,0.4); transform: rotate(315deg) translateY(-20px); animation-delay: 1.05s; }
@keyframes kf-24-petal {
  0%, 100% { transform: rotate(var(--r, 0deg)) translateY(-20px) scaleY(0.6); opacity: 0.5; }
  40%, 60% { transform: rotate(var(--r, 0deg)) translateY(-25px) scaleY(1.1); opacity: 1; }
}
/* override with inline CSS instead; use nth-child combined with CSS vars via property */
.kf-24 .petal:nth-child(1) { --r: 0deg; }
.kf-24 .petal:nth-child(2) { --r: 45deg; }
.kf-24 .petal:nth-child(3) { --r: 90deg; }
.kf-24 .petal:nth-child(4) { --r: 135deg; }
.kf-24 .petal:nth-child(5) { --r: 180deg; }
.kf-24 .petal:nth-child(6) { --r: 225deg; }
.kf-24 .petal:nth-child(7) { --r: 270deg; }
.kf-24 .petal:nth-child(8) { --r: 315deg; }
.kf-24 .lotus-core {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, rgba(255,255,255,0.3) 100%);
  z-index: 2;
  animation: kf-24-lotuscore 6s ease-in-out infinite;
}
@keyframes kf-24-lotuscore {
  0%, 100% { transform: scale(0.8); opacity: 0.5; }
  50% { transform: scale(1.1); opacity: 1; }
}

/* 5: Waveform breathing */
.kf-24 .wave-breath {
  width: 200px;
  height: 80px;
  overflow: hidden;
  position: relative;
}
.kf-24 .wave-breath svg {
  width: 100%;
  height: 100%;
}
.kf-24 .wave-breath path {
  fill: none;
  stroke: rgba(0,212,255,0.8);
  stroke-width: 2.5;
  stroke-linecap: round;
  animation: kf-24-wavepath 4s ease-in-out infinite;
}
@keyframes kf-24-wavepath {
  0%   { d: path("M 0,40 Q 50,40 100,40 Q 150,40 200,40"); opacity: 0.5; }
  25%  { d: path("M 0,40 Q 50,5  100,40 Q 150,75 200,40"); opacity: 1; }
  50%  { d: path("M 0,40 Q 50,40 100,40 Q 150,40 200,40"); opacity: 0.5; }
  75%  { d: path("M 0,40 Q 50,75 100,40 Q 150,5  200,40"); opacity: 1; }
  100% { d: path("M 0,40 Q 50,40 100,40 Q 150,40 200,40"); opacity: 0.5; }
}

/* 6: Timer circle */
.kf-24 .timer-breath {
  position: relative;
  width: 120px;
  height: 120px;
}
.kf-24 .timer-breath svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.kf-24 .timer-track {
  fill: none;
  stroke: #1e2040;
  stroke-width: 6;
}
.kf-24 .timer-prog {
  fill: none;
  stroke: url(#kf24grad);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 327;
  animation: kf-24-timerprog 8s linear infinite;
}
@keyframes kf-24-timerprog {
  0%   { stroke-dashoffset: 327; }
  100% { stroke-dashoffset: 0; }
}
.kf-24 .timer-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.4rem;
  font-weight: 300;
  color: rgba(180,200,255,0.9);
  animation: kf-24-timertext 8s linear infinite;
}
@keyframes kf-24-timertext {
  0%   { content: '8'; }
}

@media (prefers-reduced-motion: reduce) {
  .kf-24 .breathe-circle,
  .kf-24 .breathe-label,
  .kf-24 .box-dot,
  .kf-24 .box-glow,
  .kf-24 .ripple-ring,
  .kf-24 .ripple-core,
  .kf-24 .petal,
  .kf-24 .lotus-core,
  .kf-24 .wave-breath path,
  .kf-24 .timer-prog { animation: none; }
}

How this works

The classic breathing circle uses an 8s ease-in-out keyframe that takes the orb from scale(0.85) at exhale to scale(1.25) at inhale, holding 30% of the cycle at peak (matching real 4-4-4-4 breathing pacing). A box-shadow ring animates simultaneously from 0 0 0 0 to 0 0 0 20px transparent, creating an expanding-ring overlay.

Box breathing moves a 16px dot around the perimeter of a 140px square via top/left with calc(100% - 8px) values at each corner, linear timing over 16s (4s per edge). The lotus uses eight petals rotated to 0/45/90/135/180/225/270/315deg via per-child --r custom properties, all animating scaleY: 0.6 → 1.1 on a 6s cycle. The breath timer is the standard stroke-dashoffset: 327 → 0 SVG draw on a 52px-radius circle.

Customize

  • Adjust the breathing rhythm by editing the 8s duration on kf-24-breathe — match it to your meditation pacing (4-4-4-4 = 16s, box = 4-4 = 8s).
  • Recolour the orb via the radial-gradient(circle, rgba(100,160,255,...)) stops in .kf-24 .breathe-circle.
  • Change petal count by adding/removing .petal children and updating the per-child --r rotation values.
  • Match box-breathing to a different square size by editing both the .box-wrap dimensions and the calc() values in kf-24-boxbreath.
  • Slow the timer to a true 16-second cycle by changing kf-24-timerprog from 8s to 16s.

Watch out for

  • These animations exist to calm — running them at faster pacing creates the opposite effect. Don't speed them up for visual punch.
  • Box-breathing animates top and left which hits layout each frame; on long pages, this can scroll-jank — wrap in a transform: translateZ(0) container.
  • The CSS d: property animation in kf-24-wavepath is the wave-breath secret — it's Chrome/Edge only. Firefox and Safari ignore it silently.

Browser support

ChromeSafariFirefoxEdge
85+ 16.4+ 128+ 85+

Animating the SVG path d-attribute via CSS needs the newer property registration — Firefox below 128 shows a flat wave instead of breathing.

Search CodeFronts

Loading…