13 CSS Neumorphism & Soft UI Designs

Breathe

A warm sand-toned neumorphic wellness widget built around a breathing orb. Pulsing concentric-ring progress arc in terracotta gradient, three expanding pulse rings tied to the inhale/exhale cycle, animated phase-label transitions (Inhale → Hold → Exhale → Rest), a live countdown timer, cycle-progress dots, and preset duration pills. Cormorant Garamond for editorial calm. Best for meditation apps, wellness brands, mindfulness tools.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<section class="nm-bre" aria-label="Breathing meditation timer">
  <div class="card">
    <span class="ambient" aria-hidden="true"></span>

    <div class="phase-label" id="nm-bre-phase">Inhale slowly</div>

    <div class="orb-wrap">
      <div class="ring-track">
        <svg viewBox="0 0 220 220" aria-hidden="true">
          <defs>
            <linearGradient id="nm-bre-grad" x1="0%" y1="0%" x2="100%" y2="100%">
              <stop offset="0%" style="stop-color:#c4856a"/>
              <stop offset="100%" style="stop-color:#d9b8a8"/>
            </linearGradient>
          </defs>
          <circle class="track" cx="110" cy="110" r="100"/>
          <circle class="progress" cx="110" cy="110" r="100"/>
        </svg>

        <div class="orb-inner">
          <span class="pulse-ring" aria-hidden="true"></span>
          <span class="pulse-ring" aria-hidden="true"></span>
          <span class="pulse-ring" aria-hidden="true"></span>
          <span class="orb-symbol" aria-hidden="true">☽</span>
        </div>
      </div>
    </div>

    <div class="timer-display">
      <div class="time" id="nm-bre-timer">05:00</div>
      <div class="sub">minutes remaining</div>
    </div>

    <div class="cycles" aria-hidden="true">
      <span class="cycle-dot done"></span>
      <span class="cycle-dot done"></span>
      <span class="cycle-dot done"></span>
      <span class="cycle-dot"></span>
      <span class="cycle-dot"></span>
      <span class="cycle-dot"></span>
      <span class="cycle-dot"></span>
      <span class="cycle-dot"></span>
    </div>

    <div class="controls">
      <button type="button" class="btn-sm" aria-label="Restart">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
          <polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4"/>
        </svg>
      </button>

      <button type="button" class="btn-main" id="nm-bre-play" aria-label="Play / Pause">
        <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
          <polygon points="6,4 20,12 6,20"/>
        </svg>
      </button>

      <button type="button" class="btn-sm" aria-label="Settings">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
          <circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
        </svg>
      </button>
    </div>

    <div class="presets">
      <button type="button" class="preset-pill active" data-mins="5">5 min</button>
      <button type="button" class="preset-pill" data-mins="10">10 min</button>
      <button type="button" class="preset-pill" data-mins="20">20 min</button>
    </div>
  </div>
</section>
/* ─── 01 Breathe — sand-toned meditation timer ──────────────────────
   All :root vars from the source mock are scoped to .nm-bre so this
   demo's warm sand palette never leaks. Body styles (centering,
   background) are moved to the wrapper too. */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300&family=Jost:wght@200;300;400&display=swap');

.nm-bre {
  --nm-bre-bg: #e8e0d8;
  --nm-bre-shadow-dark: #c4bdb5;
  --nm-bre-shadow-light: #ffffff;
  --nm-bre-text-primary: #6b6260;
  --nm-bre-text-muted: #a09896;
  --nm-bre-accent: #c4856a;
  --nm-bre-accent-soft: #d9a08a;

  position: relative;
  width: 100%;
  min-height: 720px;
  background: var(--nm-bre-bg);
  font-family: 'Jost', system-ui, sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  overflow: hidden;
  box-sizing: border-box;
}

.nm-bre *,
.nm-bre *::before,
.nm-bre *::after { box-sizing: border-box; }

/* The inner card holds the neumorphic shadow */
.nm-bre .card {
  position: relative;
  width: 100%;
  max-width: 420px;
  background: var(--nm-bre-bg);
  border-radius: 40px;
  padding: 50px 40px 44px;
  box-shadow:
    12px 12px 28px var(--nm-bre-shadow-dark),
    -10px -10px 24px var(--nm-bre-shadow-light);
}

.nm-bre .ambient {
  position: absolute;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  top: -20px;
  right: -20px;
  background: radial-gradient(circle, rgba(196, 133, 106, 0.12), transparent 70%);
  pointer-events: none;
}

/* Phase label */
.nm-bre .phase-label {
  text-align: center;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 13px;
  font-weight: 300;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--nm-bre-text-muted);
  transition: opacity 0.8s;
}

/* Orb */
.nm-bre .orb-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 32px;
}
.nm-bre .ring-track {
  width: 220px;
  height: 220px;
  border-radius: 50%;
  background: var(--nm-bre-bg);
  box-shadow:
    8px 8px 18px var(--nm-bre-shadow-dark),
    -7px -7px 16px var(--nm-bre-shadow-light);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.nm-bre .ring-track svg {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  transform: rotate(-90deg);
}
.nm-bre .ring-track svg circle.track {
  fill: none;
  stroke: var(--nm-bre-shadow-dark);
  stroke-width: 3;
  opacity: 0.4;
}
.nm-bre .ring-track svg circle.progress {
  fill: none;
  stroke: url(#nm-bre-grad);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 628;
  stroke-dashoffset: 628;
  animation: nm-bre-ring 16s linear infinite;
}
@keyframes nm-bre-ring {
  0%   { stroke-dashoffset: 628; }
  50%  { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 628; }
}

.nm-bre .orb-inner {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 38%, #f2ebe4, #ddd5cc);
  box-shadow:
    inset 5px 5px 12px var(--nm-bre-shadow-dark),
    inset -4px -4px 10px var(--nm-bre-shadow-light);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1;
}
.nm-bre .pulse-ring {
  position: absolute;
  border-radius: 50%;
  border: 1.5px solid var(--nm-bre-accent-soft);
  opacity: 0;
  animation: nm-bre-pulse 8s ease-in-out infinite;
}
.nm-bre .pulse-ring:nth-of-type(1) { width: 90px; height: 90px; animation-delay: 0s; }
.nm-bre .pulse-ring:nth-of-type(2) { width: 120px; height: 120px; animation-delay: 2s; }
.nm-bre .pulse-ring:nth-of-type(3) { width: 150px; height: 150px; animation-delay: 4s; }
@keyframes nm-bre-pulse {
  0%   { transform: scale(0.7); opacity: 0; }
  30%  { opacity: 0.5; }
  100% { transform: scale(1.4); opacity: 0; }
}
.nm-bre .orb-symbol {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 38px;
  font-weight: 300;
  color: var(--nm-bre-text-primary);
  letter-spacing: -1px;
  animation: nm-bre-breathe 8s ease-in-out infinite;
  user-select: none;
}
@keyframes nm-bre-breathe {
  0%, 100% { transform: scale(1); opacity: 0.7; }
  50%      { transform: scale(1.06); opacity: 1; }
}

/* Timer */
.nm-bre .timer-display { text-align: center; margin-bottom: 26px; }
.nm-bre .timer-display .time {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 52px;
  font-weight: 300;
  color: var(--nm-bre-text-primary);
  letter-spacing: -1px;
  line-height: 1;
}
.nm-bre .timer-display .sub {
  font-size: 10px;
  font-weight: 300;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--nm-bre-text-muted);
  margin-top: 6px;
}

/* Cycle dots */
.nm-bre .cycles {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-bottom: 30px;
}
.nm-bre .cycle-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--nm-bre-bg);
  box-shadow:
    2px 2px 5px var(--nm-bre-shadow-dark),
    -2px -2px 4px var(--nm-bre-shadow-light);
  transition: all 0.4s;
}
.nm-bre .cycle-dot.done {
  background: var(--nm-bre-accent-soft);
  box-shadow:
    inset 1px 1px 3px rgba(0, 0, 0, 0.2),
    inset -1px -1px 3px rgba(255, 255, 255, 0.4);
}

/* Controls */
.nm-bre .controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
}
.nm-bre .btn-sm {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: none;
  background: var(--nm-bre-bg);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    5px 5px 12px var(--nm-bre-shadow-dark),
    -4px -4px 10px var(--nm-bre-shadow-light);
  color: var(--nm-bre-text-muted);
  font-size: 14px;
  transition: all 0.2s;
}
.nm-bre .btn-sm:active {
  box-shadow:
    inset 3px 3px 8px var(--nm-bre-shadow-dark),
    inset -2px -2px 6px var(--nm-bre-shadow-light);
}
.nm-bre .btn-main {
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: none;
  background: var(--nm-bre-bg);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    8px 8px 18px var(--nm-bre-shadow-dark),
    -7px -7px 16px var(--nm-bre-shadow-light);
  color: var(--nm-bre-accent);
  font-size: 22px;
  transition: all 0.2s;
  position: relative;
}
.nm-bre .btn-main::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: linear-gradient(135deg, #f0e9e2, #dcd4cc);
  z-index: 0;
}
.nm-bre .btn-main svg { position: relative; z-index: 1; }
.nm-bre .btn-main:active {
  box-shadow:
    inset 4px 4px 10px var(--nm-bre-shadow-dark),
    inset -3px -3px 8px var(--nm-bre-shadow-light);
}

/* Presets */
.nm-bre .presets {
  display: flex;
  justify-content: center;
  gap: 8px;
}
.nm-bre .preset-pill {
  padding: 6px 16px;
  border-radius: 20px;
  border: none;
  background: var(--nm-bre-bg);
  font-family: 'Jost', system-ui, sans-serif;
  font-size: 10px;
  font-weight: 300;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--nm-bre-text-muted);
  cursor: pointer;
  box-shadow:
    3px 3px 8px var(--nm-bre-shadow-dark),
    -2px -2px 6px var(--nm-bre-shadow-light);
  transition: all 0.2s;
}
.nm-bre .preset-pill.active {
  color: var(--nm-bre-accent);
  box-shadow:
    inset 2px 2px 5px var(--nm-bre-shadow-dark),
    inset -2px -2px 4px var(--nm-bre-shadow-light);
}

@media (prefers-reduced-motion: reduce) {
  .nm-bre .ring-track svg circle.progress,
  .nm-bre .pulse-ring,
  .nm-bre .orb-symbol { animation: none; }
}
(() => {
  const root = document.querySelector('.nm-bre');
  if (!root) return;
  const phases = ['Inhale slowly', 'Hold gently', 'Exhale softly', 'Rest'];
  const durations = [4000, 2000, 6000, 2000];
  let phaseIdx = 0;
  const phaseLabel = root.querySelector('#nm-bre-phase');

  function nextPhase() {
    phaseIdx = (phaseIdx + 1) % phases.length;
    if (phaseLabel) {
      phaseLabel.style.opacity = 0;
      setTimeout(() => {
        phaseLabel.textContent = phases[phaseIdx];
        phaseLabel.style.opacity = 1;
      }, 400);
    }
    setTimeout(nextPhase, durations[phaseIdx]);
  }
  setTimeout(nextPhase, durations[0]);

  let totalSecs = 300;
  let timerInterval = null;
  let running = false;
  const timerEl = root.querySelector('#nm-bre-timer');
  const playBtn = root.querySelector('#nm-bre-play');

  const fmt = (s) => String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
  const playIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><polygon points="6,4 20,12 6,20"/></svg>';
  const pauseIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>';

  if (playBtn) playBtn.addEventListener('click', () => {
    running = !running;
    if (running) {
      playBtn.innerHTML = pauseIcon;
      timerInterval = setInterval(() => {
        if (totalSecs > 0) {
          totalSecs--;
          if (timerEl) timerEl.textContent = fmt(totalSecs);
        } else {
          clearInterval(timerInterval);
          running = false;
          playBtn.innerHTML = playIcon;
        }
      }, 1000);
    } else {
      clearInterval(timerInterval);
      playBtn.innerHTML = playIcon;
    }
  });

  root.querySelectorAll('.preset-pill').forEach(btn => {
    btn.addEventListener('click', () => {
      root.querySelector('.preset-pill.active')?.classList.remove('active');
      btn.classList.add('active');
      const mins = parseInt(btn.dataset.mins, 10) || 5;
      totalSecs = mins * 60;
      if (timerEl) timerEl.textContent = fmt(totalSecs);
      if (running) {
        clearInterval(timerInterval);
        running = false;
        if (playBtn) playBtn.innerHTML = playIcon;
      }
    });
  });
})();

Search CodeFronts

Loading…