Text✨ Premium

Breathing Text

Pure CSS animation — each word pulses between scale(1) and scale(1.08) over 3 seconds, staggered by an individual --delay variable to create a living breath without any JavaScript.

CSSAnimationScale

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. Text has 56 effects, including 4 free. Explore the category →

3 usage examples

Launch hero — Living three-word tagline — Breathing Text example 1

① Launch hero — Living three-word tagline

WhenA SaaS or app home page displays its promise in 3 key words that each breathe at their own rhythm, creating an animated presence without network requests or video.
WhyA scale of 1 to 1.08 gives visual weight to each word without triggering layout reflow — adjacent words stay perfectly still. The left-to-right wave naturally guides the eye across the message.
Settings3 words, --delay: 0s / 0.4s / 0.8s, animation-duration: 4s (more solemn), font-size: clamp(3rem, 8vw, 6rem), font-weight: 900.
Meditation guide — 4-7-8 breathing instruction — Breathing Text example 2

② Meditation guide — 4-7-8 breathing instruction

WhenIn a meditation or wellness app, the words INHALE, HOLD, EXHALE animate to visually accompany a guided breathing exercise.
WhyThe word semantics match the animation exactly — the text literally breathes. Each word can carry its own cycle duration via inline animation-duration, tuned to the physiological 4-7-8 timing.
Settingsanimation-duration per span: 4s (INHALE), 7s (HOLD), 8s (EXHALE); scale(1.05) in the keyframe for a softer amplitude; color: #a5f3fc.
Focus mode — Subliminal activity indicator — Breathing Text example 3

③ Focus mode — Subliminal activity indicator

WhenIn a dashboard or productivity tool in focus mode, a living state indicator replaces a static 'Loading…' label without distracting the user.
WhyThe low amplitude (scale 1 → 1.05) and slow cycle (5 s) make the animation nearly subliminal — the user senses the page is alive without being disrupted. Works well alongside a timer or progress bar.
Settings2 words, --delay: 0s / 1.5s, animation-duration: 5s, scale(1.05) in the keyframe, font-size: 0.85rem, color: rgba(255,255,255,0.55).

How it works

The effect is driven by @keyframes ktBreathe: at 0% and 100%, each word sits at transform: scale(1) and opacity: 0.85; at the 50% peak it reaches scale(1.08) and opacity: 1. The ease-in-out curve over 3 seconds mimics a slow inhale and exhale. The animation runs infinitely on the .kt-breathing .word selector — display: inline-block is required because CSS transforms have no effect on bare inline elements.

Per-word staggering is achieved through a CSS custom property --delay placed in each <span class="word">'s style attribute. The rule animation-delay: var(--delay) reads this value element by element, with zero JavaScript. Adding a word is as simple as inserting a <span class="word" style="--delay: Xs"> — the step between words is entirely free.

The JS block defines a splitChars(el) helper (splits data-text into .char spans) and selects an element via getElementById('fadeInText'), but neither the function nor the variable is ever called at runtime — this is utility code inherited from another effect. The breathing animation runs without JS: the <script> block can safely be removed or replaced.

Each .word expands from its center thanks to the default transform-origin: 50% 50% — no layout shift, adjacent words stay put. The absence of will-change avoids reserving a GPU compositing layer at rest, keeping memory overhead low when multiple instances share the page.

Accessibility

  • prefers-reduced-motion: absent. No @media (prefers-reduced-motion: reduce) block is present in the CSS. The animation loops forever even when the OS signals a motion-reduction preference. Recommended addition: @media (prefers-reduced-motion: reduce) { .kt-breathing .word { animation: none; } }.
  • No aria-* attributes or role in the provided HTML. For purely decorative text this is acceptable; if the text carries meaning for screen readers, wrap the container in a <p aria-label="full text"> and add aria-hidden="true" to each <span class="word">.
  • Contrast: white (#fff) on dark background (#0a0a0f) → ratio > 20:1, well above the WCAG AA threshold (4.5:1).
  • No interactive element in the effect — keyboard navigation is unaffected, no keyboard event listeners are attached.
  • The animation involves no flash, rapid flickering, or large-amplitude movement (scale 1 to 1.08 over 3 s) — no risk of triggering photosensitive seizures.

Browser compatibility

Requires CSS Custom Properties and CSS Animations — supported in all modern browsers. Zero external dependencies.

Chrome 65+✓ Full
Firefox 55+✓ Full
Safari 12+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without CSS custom property support (very old browsers), <code>animation-delay</code> falls back to 0 s (the initial value) — all words breathe in unison instead of staggering. The breathing effect remains present, just unsynchronized.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="kt-text kt-breathing" data-text="BREATHE IN OUT">
  <span class="word" style="--delay: 0s;">BREATHE</span>
  <span class="word" style="--delay: 0.5s;">IN</span>
  <span class="word" style="--delay: 1s;">OUT</span>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
--delay (inline on each .word) 0s, 0.5s, 1s… Per-word animation offset. Increase the step for a more pronounced wave, reduce it for near-simultaneous breathing.
animation-duration (in .kt-breathing .word CSS rule) 3s Full cycle duration. 2 s = nervous, 5–6 s = meditative. Can also be overridden per word via inline style.
scale() at 50% in @keyframes ktBreathe scale(1.08) Maximum breath amplitude. scale(1.04) = subtle, scale(1.15) = dramatic. Above scale(1.2), adjacent words begin to overlap.
opacity at 0%/100% in @keyframes ktBreathe 0.85 Resting opacity (trough of the breath). Set to 0.6 for a more marked pulse, 1 to suppress the opacity oscillation entirely.
font-size in .kt-text 2.2rem Text size. The effect reads clearly from 1.5 rem upward — below that, a scale of 1.08 is barely perceptible.
color in .kt-text #fff Text color. Any hex or rgba works; a light tint (e.g. #a5f3fc ice blue) accentuates a spiritual or organic register.
letter-spacing in .kt-text 0.02em Inter-letter spacing. Increase to 0.08em or more for a monumental look suited to posters or full-screen slides.
margin in .kt-breathing .word 0 0.15em Inter-word spacing. Increase so each word has its own halo and does not encroach on neighbours during the scale.
animation-timing-function in .kt-breathing .word ease-in-out Easing curve. linear = mechanical, ease-in-out = natural/respiratory. cubic-bezier(0.45,0,0.55,1) produces an even softer breath.

FAQ

No. The JS block defines a splitChars() helper and selects an element, but neither is ever called at runtime. The breathing animation is fully CSS-driven — the ktBreathe keyframe and the --delay variable are all it needs. The <script> block can be removed without breaking anything.
Add style="animation-duration: 4s" to the 'INHALE' span, 7s to 'HOLD', and 8s to 'EXHALE'. Each word can carry its own duration: animation-duration is inherited but overridden via the inline style attribute, and each word's cycle runs independently.
Yes. The splitChars(el) function already in the JS splits data-text into <span class="char"> elements. Call it, replace .word with .char in the .kt-breathing CSS rule, and assign --delay incrementally (e.g. 0.08 s per letter) to get a letter-by-letter breathing effect with the same keyframe.