Box Breathing

Four seconds in, four hold, four out, four hold: no app, no account, no ads mid-cycle. Use it right here, or copy the code below into your own project.

Ready
Cycles: 0

Why Box Breathing Works

Box breathing pairs an equal-count inhale, hold, exhale, and hold, four seconds each, into one repeating square. The even counts are the point: an extended exhale relative to the inhale is what actually signals safety to the nervous system, so the fixed 4-4-4-4 rhythm here isn't arbitrary, it's the shape that gets a stressed body to downshift the fastest.

The Physiology of the 4-4-4-4 Pattern

Slow, deliberate breathing engages the vagus nerve and nudges the body from a sympathetic "on alert" state toward a parasympathetic "safe to rest" one. The hold at the top and bottom of each cycle isn't dead time, it's what keeps the breath from turning shallow and fast again the moment attention drifts, which is the usual way a good exhale gets undone thirty seconds later.

When to Use It

Before a call you're dreading, right after a deploy that didn't go the way you hoped, or in the gap between closing one problem and opening the next. It's short enough to fit inside a single context switch, which is exactly when a nervous system needs it most and has the least patience for anything longer.

Getting the Timing Right

The pattern traces back to military and tactical training precisely because it's simple enough to run under pressure without counting in your head. That's what this timer is actually for: it holds the exact four-second phases so you can stop tracking the count yourself and just follow the circle.

Copy the Code

Plain HTML, CSS, and JavaScript, no dependencies, no build step. Comments walk through what each block is doing, so it's easy to adapt rather than just paste and forget.

<!-- Box Breathing -- copy this whole block into any HTML page. -->
<!-- No build step, no dependencies -- plain HTML, CSS, and JS. -->

<div id="box-breathing">
  <div class="bb-circle-wrap">
    <div class="bb-circle" id="bbCircle">
      <span id="bbPhase">Ready</span>
    </div>
  </div>
  <div class="bb-cycles" id="bbCycles">Cycles: 0</div>
  <div class="bb-controls">
    <button id="bbStart" type="button">Start</button>
    <button id="bbReset" type="button">Reset</button>
  </div>
</div>

<style>
  /* Minimal styling -- restyle freely to match your own site. */
  #box-breathing { font-family: sans-serif; max-width: 260px; text-align: center; margin: 0 auto; }
  .bb-circle-wrap { display: flex; justify-content: center; margin-bottom: 12px; }
  .bb-circle {
    width: 140px;
    height: 140px;
    border-radius: 50%;
    border: 2px solid #4de8ff;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: boxBreathe 16s ease-in-out infinite;
    animation-play-state: paused;
  }
  .bb-circle span { font-weight: 700; }
  .bb-cycles { font-size: 0.8rem; opacity: 0.7; margin-bottom: 12px; }
  .bb-controls button { padding: 8px 16px; margin: 0 4px; cursor: pointer; }

  /* Box breathing is 4s in, 4s hold, 4s out, 4s hold -- 16s per full cycle.
     The circle grows on the inhale, holds full-size, shrinks on the
     exhale, then holds small again before the next cycle starts. */
  @keyframes boxBreathe {
    0%   { transform: scale(0.6); }
    25%  { transform: scale(1); }
    50%  { transform: scale(1); }
    75%  { transform: scale(0.6); }
    100% { transform: scale(0.6); }
  }

  @media (prefers-reduced-motion: reduce) {
    .bb-circle { animation: none; }
  }
</style>

<script>
(function () {
  // Grab all the elements we need up front.
  var circleEl = document.getElementById('bbCircle');
  var phaseEl = document.getElementById('bbPhase');
  var cyclesEl = document.getElementById('bbCycles');
  var startBtn = document.getElementById('bbStart');
  var resetBtn = document.getElementById('bbReset');

  // The four phases of one box-breathing cycle, in order. Each one
  // lasts 4 seconds, matching the 16s CSS animation above.
  var phases = ['Inhale', 'Hold', 'Exhale', 'Hold'];
  var phaseIndex = 0;
  var cycleCount = 0;
  var timerId = null;

  // Forces the CSS animation back to frame 0 by removing it, forcing
  // a reflow, then reapplying it -- otherwise restarting mid-animation
  // would jump straight to wherever the browser last left it.
  function restartAnimation() {
    circleEl.style.animation = 'none';
    void circleEl.offsetHeight;
    circleEl.style.animation = '';
  }

  function start() {
    if (timerId) return;
    phaseIndex = 0;
    cycleCount = 0;
    phaseEl.textContent = phases[phaseIndex];
    cyclesEl.textContent = 'Cycles: 0';
    restartAnimation();
    circleEl.style.animationPlayState = 'running';

    // Step the phase label forward every 4 seconds, in sync with the
    // 16s animation (4 phases x 4s each). One full lap back to
    // "Inhale" counts as one completed cycle.
    timerId = setInterval(function () {
      phaseIndex = (phaseIndex + 1) % phases.length;
      phaseEl.textContent = phases[phaseIndex];
      if (phaseIndex === 0) {
        cycleCount += 1;
        cyclesEl.textContent = 'Cycles: ' + cycleCount;
      }
    }, 4000);
  }

  function reset() {
    clearInterval(timerId);
    timerId = null;
    phaseIndex = 0;
    cycleCount = 0;
    phaseEl.textContent = 'Ready';
    cyclesEl.textContent = 'Cycles: 0';
    circleEl.style.animationPlayState = 'paused';
    restartAnimation();
  }

  startBtn.addEventListener('click', start);
  resetBtn.addEventListener('click', reset);
})();
</script>