No pulsing attention loops

Status dots and CTAs stay still; a color and a word carry the state.

From design-motion-principles, anti-checklist, Pulsing indicators.

Six services, one goes down

Break a service. Which pane shows the outage first?

Every dot pulsesBad
    animation: pulse 2s infinite
    Static dot + labelGood
      animation: none

      .status { display: inline-flex; align-items: center; gap: 6px; }
      .status::before {
        content: ""; width: 8px; height: 8px; border-radius: 50%;
        background: currentColor;
      }
      .status[data-state="ok"]   { color: var(--good); }
      .status[data-state="down"] { color: var(--bad); font-weight: 600; }
      
      <span class="status" data-state="down">Down</span>

      Breathing CTA vs static button

      Type a name in each pane. Where does your eye go?

      Breathes foreverBad

      animation: breathe 2s ease-in-out infinite
      Enables when readyGood

      button.disabled = !valid
      .btn-primary {
        background: var(--accent); color: var(--accent-ink);
        transition: transform 160ms var(--ease-out), background-color 150ms ease;
      }
      .btn-primary:active   { transform: scale(0.97); }
      .btn-primary:disabled { opacity: .5; }
      
      input.addEventListener("input", () => {
        button.disabled = input.value.trim() === "";
      });

      Let the data prove it's live

      Press Record, then Stall the stream.

      Pulsing ringBad
      Not recording
      animation: ping 1s infinite
      Static dot + timerGood
      REC 00:00
      time.textContent = fmt(elapsed)

      .rec-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--bad); }
      .rec[data-state="stalled"] .rec-dot { background: var(--warn); }
      .rec-time { font-variant-numeric: tabular-nums; }
      
      stream.on("chunk", (c) => {
        elapsed += c.duration;
        time.textContent = fmt(elapsed);
        lastChunk = performance.now();
      });
      setInterval(() => {
        rec.dataset.state = performance.now() - lastChunk > 2000 ? "stalled" : "live";
      }, 500);

      Why it matters, pros and cons

      Why it matters

      A loop that never stops means nothing, and still says “live” after data dies.

      Pros

      • Real changes get the contrast
      • Nothing to pause for WCAG 2.2.2
      • Liveness proven by data, not decoration

      Cons

      • One brand element with a written reason may pulse
      • A one-shot pulse on a new event is fine
      • Needs a label or shape, not color alone