30 CSS Text Hover Effects23 / 30
Smoke / Dissolve Particle Effect
Hold the cursor on the title and it exhales: each letter blurs, lightens, drifts up along its own slightly different heading, and thins to nothing — smoke leaving a cooling fire. Pull away mid-dissolve and it re-condenses through the exact same path, because everything is transitions (never keyframes): per-letter drift vectors and rotations live in inline custom properties, delays stagger 35ms per letter, and filter:blur() is the actual smoke. The full word stays in the accessibility tree via aria-label while the letter spans do the vanishing.
Published
The code
<section class="cth-23" aria-label="Smoke dissolve text demo">
<div class="cth-23__stage">
<div class="cth-23__scene">
<p class="cth-23__eyebrow">A film by Ines Marlowe</p>
<h1 class="cth-23__title" tabindex="0" aria-label="AFTERGLOW">
<span aria-hidden="true"><i style="--i:0;--dx:-.1em;--dr:-8deg">A</i><i style="--i:1;--dx:.06em;--dr:5deg">F</i><i style="--i:2;--dx:-.04em;--dr:-4deg">T</i><i style="--i:3;--dx:.14em;--dr:9deg">E</i><i style="--i:4;--dx:-.12em;--dr:-6deg">R</i><i style="--i:5;--dx:.05em;--dr:7deg">G</i><i style="--i:6;--dx:-.07em;--dr:-9deg">L</i><i style="--i:7;--dx:.11em;--dr:4deg">O</i><i style="--i:8;--dx:-.05em;--dr:-5deg">W</i></span>
</h1>
<p class="cth-23__sub">In cinemas October — the story of everything we let cool.</p>
<p class="cth-23__hint">Rest the cursor on the title and hold; pull away mid-dissolve and it re-condenses along the same path. Transitions, never keyframes — that's the whole trick.</p>
</div>
</div>
</section><section class="cth-23" aria-label="Smoke dissolve text demo">
<div class="cth-23__stage">
<div class="cth-23__scene">
<p class="cth-23__eyebrow">A film by Ines Marlowe</p>
<h1 class="cth-23__title" tabindex="0" aria-label="AFTERGLOW">
<span aria-hidden="true"><i style="--i:0;--dx:-.1em;--dr:-8deg">A</i><i style="--i:1;--dx:.06em;--dr:5deg">F</i><i style="--i:2;--dx:-.04em;--dr:-4deg">T</i><i style="--i:3;--dx:.14em;--dr:9deg">E</i><i style="--i:4;--dx:-.12em;--dr:-6deg">R</i><i style="--i:5;--dx:.05em;--dr:7deg">G</i><i style="--i:6;--dx:-.07em;--dr:-9deg">L</i><i style="--i:7;--dx:.11em;--dr:4deg">O</i><i style="--i:8;--dx:-.05em;--dr:-5deg">W</i></span>
</h1>
<p class="cth-23__sub">In cinemas October — the story of everything we let cool.</p>
<p class="cth-23__hint">Rest the cursor on the title and hold; pull away mid-dissolve and it re-condenses along the same path. Transitions, never keyframes — that's the whole trick.</p>
</div>
</div>
</section>.cth-23,
.cth-23 *,
.cth-23 *::before,
.cth-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-23 {
--bg: oklch(0.15 0.008 270);
--ink: oklch(0.9 0.01 80);
--mut: oklch(0.56 0.015 275);
font-family: Georgia,'Times New Roman',serif;
color: var(--ink);
}
.cth-23__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 90% at 50% 100%,oklch(0.2 0.02 50/.55),transparent 55%),radial-gradient(90% 70% at 50% 20%,oklch(0.18 0.01 275),var(--bg) 75%);
padding: clamp(20px,4vw,56px);
container: cth23/inline-size;
}
.cth-23__scene {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: clamp(16px,3cqi,26px);
}
.cth-23__eyebrow {
font-size: 12px;
letter-spacing: .34em;
text-transform: uppercase;
color: var(--mut);
font-family: 'Segoe UI',system-ui,sans-serif;
font-weight: 600;
}
.cth-23__title {
padding: clamp(24px,5cqi,48px) clamp(16px,3cqi,32px);
letter-spacing: .16em;
border-radius: 14px;
}
.cth-23__title:focus-visible {
outline: 1.5px solid var(--ink);
outline-offset: 4px;
}
.cth-23__title i {
display: inline-block;
font-style: normal;
font-size: clamp(44px,10cqi,104px);
font-weight: 500;
line-height: 1;
translate: 0 0;
rotate: 0deg;
opacity: 1;
filter: blur(0);
transition: translate 1s cubic-bezier(.3,.6,.2,1),rotate 1s ease,opacity 1s ease,filter 1s ease,color 1s ease;
transition-delay: calc(var(--i)*35ms);
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: var(--dx) -1.25em;
rotate: var(--dr);
opacity: 0;
filter: blur(.14em);
color: oklch(0.98 0.005 80);
}
.cth-23__sub {
font-size: 14.5px;
font-style: italic;
color: var(--mut);
letter-spacing: .02em;
}
.cth-23__hint {
font-size: 12.5px;
line-height: 1.7;
color: var(--mut);
max-width: 56ch;
font-family: 'Segoe UI',system-ui,sans-serif;
}
@media (prefers-reduced-motion: reduce) {
.cth-23__title i {
transition: opacity .4s ease !important;
transition-delay: 0s !important;
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: 0 0;
rotate: 0deg;
filter: blur(0);
opacity: .45;
}
}.cth-23,
.cth-23 *,
.cth-23 *::before,
.cth-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-23 {
--bg: oklch(0.15 0.008 270);
--ink: oklch(0.9 0.01 80);
--mut: oklch(0.56 0.015 275);
font-family: Georgia,'Times New Roman',serif;
color: var(--ink);
}
.cth-23__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 90% at 50% 100%,oklch(0.2 0.02 50/.55),transparent 55%),radial-gradient(90% 70% at 50% 20%,oklch(0.18 0.01 275),var(--bg) 75%);
padding: clamp(20px,4vw,56px);
container: cth23/inline-size;
}
.cth-23__scene {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: clamp(16px,3cqi,26px);
}
.cth-23__eyebrow {
font-size: 12px;
letter-spacing: .34em;
text-transform: uppercase;
color: var(--mut);
font-family: 'Segoe UI',system-ui,sans-serif;
font-weight: 600;
}
.cth-23__title {
padding: clamp(24px,5cqi,48px) clamp(16px,3cqi,32px);
letter-spacing: .16em;
border-radius: 14px;
}
.cth-23__title:focus-visible {
outline: 1.5px solid var(--ink);
outline-offset: 4px;
}
.cth-23__title i {
display: inline-block;
font-style: normal;
font-size: clamp(44px,10cqi,104px);
font-weight: 500;
line-height: 1;
translate: 0 0;
rotate: 0deg;
opacity: 1;
filter: blur(0);
transition: translate 1s cubic-bezier(.3,.6,.2,1),rotate 1s ease,opacity 1s ease,filter 1s ease,color 1s ease;
transition-delay: calc(var(--i)*35ms);
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: var(--dx) -1.25em;
rotate: var(--dr);
opacity: 0;
filter: blur(.14em);
color: oklch(0.98 0.005 80);
}
.cth-23__sub {
font-size: 14.5px;
font-style: italic;
color: var(--mut);
letter-spacing: .02em;
}
.cth-23__hint {
font-size: 12.5px;
line-height: 1.7;
color: var(--mut);
max-width: 56ch;
font-family: 'Segoe UI',system-ui,sans-serif;
}
@media (prefers-reduced-motion: reduce) {
.cth-23__title i {
transition: opacity .4s ease !important;
transition-delay: 0s !important;
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: 0 0;
rotate: 0deg;
filter: blur(0);
opacity: .45;
}
}Here's a working CSS Text Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Smoke / Dissolve Particle Effect
Source: https://codefronts.com/motion/css-text-hover-effects/smoke-dissolve-particle-effect/
Hold the cursor on the title and it exhales: each letter blurs, lightens, drifts up along its own slightly different heading, and thins to nothing — smoke leaving a cooling fire. Pull away mid-dissolve and it re-condenses through the exact same path, because everything is transitions (never keyframes): per-letter drift vectors and rotations live in inline custom properties, delays stagger 35ms per letter, and filter:blur() is the actual smoke. The full word stays in the accessibility tree via aria-label while the letter spans do the vanishing.
## HTML
```html
<section class="cth-23" aria-label="Smoke dissolve text demo">
<div class="cth-23__stage">
<div class="cth-23__scene">
<p class="cth-23__eyebrow">A film by Ines Marlowe</p>
<h1 class="cth-23__title" tabindex="0" aria-label="AFTERGLOW">
<span aria-hidden="true"><i style="--i:0;--dx:-.1em;--dr:-8deg">A</i><i style="--i:1;--dx:.06em;--dr:5deg">F</i><i style="--i:2;--dx:-.04em;--dr:-4deg">T</i><i style="--i:3;--dx:.14em;--dr:9deg">E</i><i style="--i:4;--dx:-.12em;--dr:-6deg">R</i><i style="--i:5;--dx:.05em;--dr:7deg">G</i><i style="--i:6;--dx:-.07em;--dr:-9deg">L</i><i style="--i:7;--dx:.11em;--dr:4deg">O</i><i style="--i:8;--dx:-.05em;--dr:-5deg">W</i></span>
</h1>
<p class="cth-23__sub">In cinemas October — the story of everything we let cool.</p>
<p class="cth-23__hint">Rest the cursor on the title and hold; pull away mid-dissolve and it re-condenses along the same path. Transitions, never keyframes — that's the whole trick.</p>
</div>
</div>
</section>
```
## CSS
```css
.cth-23,
.cth-23 *,
.cth-23 *::before,
.cth-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-23 {
--bg: oklch(0.15 0.008 270);
--ink: oklch(0.9 0.01 80);
--mut: oklch(0.56 0.015 275);
font-family: Georgia,'Times New Roman',serif;
color: var(--ink);
}
.cth-23__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 90% at 50% 100%,oklch(0.2 0.02 50/.55),transparent 55%),radial-gradient(90% 70% at 50% 20%,oklch(0.18 0.01 275),var(--bg) 75%);
padding: clamp(20px,4vw,56px);
container: cth23/inline-size;
}
.cth-23__scene {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: clamp(16px,3cqi,26px);
}
.cth-23__eyebrow {
font-size: 12px;
letter-spacing: .34em;
text-transform: uppercase;
color: var(--mut);
font-family: 'Segoe UI',system-ui,sans-serif;
font-weight: 600;
}
.cth-23__title {
padding: clamp(24px,5cqi,48px) clamp(16px,3cqi,32px);
letter-spacing: .16em;
border-radius: 14px;
}
.cth-23__title:focus-visible {
outline: 1.5px solid var(--ink);
outline-offset: 4px;
}
.cth-23__title i {
display: inline-block;
font-style: normal;
font-size: clamp(44px,10cqi,104px);
font-weight: 500;
line-height: 1;
translate: 0 0;
rotate: 0deg;
opacity: 1;
filter: blur(0);
transition: translate 1s cubic-bezier(.3,.6,.2,1),rotate 1s ease,opacity 1s ease,filter 1s ease,color 1s ease;
transition-delay: calc(var(--i)*35ms);
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: var(--dx) -1.25em;
rotate: var(--dr);
opacity: 0;
filter: blur(.14em);
color: oklch(0.98 0.005 80);
}
.cth-23__sub {
font-size: 14.5px;
font-style: italic;
color: var(--mut);
letter-spacing: .02em;
}
.cth-23__hint {
font-size: 12.5px;
line-height: 1.7;
color: var(--mut);
max-width: 56ch;
font-family: 'Segoe UI',system-ui,sans-serif;
}
@media (prefers-reduced-motion: reduce) {
.cth-23__title i {
transition: opacity .4s ease !important;
transition-delay: 0s !important;
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: 0 0;
rotate: 0deg;
filter: blur(0);
opacity: .45;
}
}
```Here's a working CSS Text Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Smoke / Dissolve Particle Effect
Source: https://codefronts.com/motion/css-text-hover-effects/smoke-dissolve-particle-effect/
Hold the cursor on the title and it exhales: each letter blurs, lightens, drifts up along its own slightly different heading, and thins to nothing — smoke leaving a cooling fire. Pull away mid-dissolve and it re-condenses through the exact same path, because everything is transitions (never keyframes): per-letter drift vectors and rotations live in inline custom properties, delays stagger 35ms per letter, and filter:blur() is the actual smoke. The full word stays in the accessibility tree via aria-label while the letter spans do the vanishing.
## HTML
```html
<section class="cth-23" aria-label="Smoke dissolve text demo">
<div class="cth-23__stage">
<div class="cth-23__scene">
<p class="cth-23__eyebrow">A film by Ines Marlowe</p>
<h1 class="cth-23__title" tabindex="0" aria-label="AFTERGLOW">
<span aria-hidden="true"><i style="--i:0;--dx:-.1em;--dr:-8deg">A</i><i style="--i:1;--dx:.06em;--dr:5deg">F</i><i style="--i:2;--dx:-.04em;--dr:-4deg">T</i><i style="--i:3;--dx:.14em;--dr:9deg">E</i><i style="--i:4;--dx:-.12em;--dr:-6deg">R</i><i style="--i:5;--dx:.05em;--dr:7deg">G</i><i style="--i:6;--dx:-.07em;--dr:-9deg">L</i><i style="--i:7;--dx:.11em;--dr:4deg">O</i><i style="--i:8;--dx:-.05em;--dr:-5deg">W</i></span>
</h1>
<p class="cth-23__sub">In cinemas October — the story of everything we let cool.</p>
<p class="cth-23__hint">Rest the cursor on the title and hold; pull away mid-dissolve and it re-condenses along the same path. Transitions, never keyframes — that's the whole trick.</p>
</div>
</div>
</section>
```
## CSS
```css
.cth-23,
.cth-23 *,
.cth-23 *::before,
.cth-23 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-23 {
--bg: oklch(0.15 0.008 270);
--ink: oklch(0.9 0.01 80);
--mut: oklch(0.56 0.015 275);
font-family: Georgia,'Times New Roman',serif;
color: var(--ink);
}
.cth-23__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 90% at 50% 100%,oklch(0.2 0.02 50/.55),transparent 55%),radial-gradient(90% 70% at 50% 20%,oklch(0.18 0.01 275),var(--bg) 75%);
padding: clamp(20px,4vw,56px);
container: cth23/inline-size;
}
.cth-23__scene {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: clamp(16px,3cqi,26px);
}
.cth-23__eyebrow {
font-size: 12px;
letter-spacing: .34em;
text-transform: uppercase;
color: var(--mut);
font-family: 'Segoe UI',system-ui,sans-serif;
font-weight: 600;
}
.cth-23__title {
padding: clamp(24px,5cqi,48px) clamp(16px,3cqi,32px);
letter-spacing: .16em;
border-radius: 14px;
}
.cth-23__title:focus-visible {
outline: 1.5px solid var(--ink);
outline-offset: 4px;
}
.cth-23__title i {
display: inline-block;
font-style: normal;
font-size: clamp(44px,10cqi,104px);
font-weight: 500;
line-height: 1;
translate: 0 0;
rotate: 0deg;
opacity: 1;
filter: blur(0);
transition: translate 1s cubic-bezier(.3,.6,.2,1),rotate 1s ease,opacity 1s ease,filter 1s ease,color 1s ease;
transition-delay: calc(var(--i)*35ms);
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: var(--dx) -1.25em;
rotate: var(--dr);
opacity: 0;
filter: blur(.14em);
color: oklch(0.98 0.005 80);
}
.cth-23__sub {
font-size: 14.5px;
font-style: italic;
color: var(--mut);
letter-spacing: .02em;
}
.cth-23__hint {
font-size: 12.5px;
line-height: 1.7;
color: var(--mut);
max-width: 56ch;
font-family: 'Segoe UI',system-ui,sans-serif;
}
@media (prefers-reduced-motion: reduce) {
.cth-23__title i {
transition: opacity .4s ease !important;
transition-delay: 0s !important;
}
.cth-23__title:hover i,
.cth-23__title:focus-visible i {
translate: 0 0;
rotate: 0deg;
filter: blur(0);
opacity: .45;
}
}
```How this works
One transition set, four channels, per-letter randomness injected as data:
<i style='--i:3; --dx:.14em; --dr:9deg'>E</i>
.title i{ display:inline-block;
translate:0 0; rotate:0deg; opacity:1; filter:blur(0);
transition:translate 1s cubic-bezier(.3,.6,.2,1),
rotate 1s ease, opacity 1s ease, filter 1s ease;
transition-delay:calc(var(--i) * 35ms); }
.title:hover i,
.title:focus-visible i{
translate:var(--dx) -1.25em; /* each letter's own drift */
rotate:var(--dr);
opacity:0;
filter:blur(.14em); }Real smoke never rises straight: the --dx values alternate small positive/negative drifts and --dr tips each letter a few degrees clockwise or counter — hand-authored 'randomness' that costs nothing at runtime and renders identically every visit (deterministic beats Math.random() for brand motion). Blur is what makes it smoke rather than confetti: edges soften as the glyph thins, exactly how vapor loses definition. The em-based drift distances scale with the font automatically.
The reversibility is the emotional payoff and it's free: transitions run backward when the hover ends, same delays, same curve — leave halfway and the word inhales back together. This is the demo to study for the transitions-vs-keyframes decision: demo 19 chose keyframes because a bounce is a script; smoke chooses transitions because dissolving is a STATE you can enter and leave. Under reduced motion the letters simply fade a little — the metaphor survives without the motion.
Make it yours
- Dissolve distance: -1.25em rises like a candle; -0.4em with double blur reads as fog on glass; positive Y values SINK the word — dissolve-down reads as drowning, striking for horror one-sheets.
- Tempo: 1s here. 2.5s with 60ms delays is a long cinematic exhale for landing-page titles; 400ms is a UI-grade puff for deleting items from a list.
- Direction sets meaning: all --dx positive = wind from the left; alternating = still air. Author the weather.
- Trigger it by class instead of :hover (.is-leaving) and the same CSS becomes an exit animation for route changes — the letters genuinely leave the page.
Gotchas — read before shipping
- Transitions, not keyframes, are load-bearing: keyframes would restart from solid on every hover and snap on exit. If someone 'optimizes' this into an animation, the re-condense dies.
- blur() past ~.2em at display sizes costs real paint on 4K screens × letter count — this is a hero-title effect, not a nav treatment. Budget by (letters × blur radius × font size).
- The hover target must stay stable while letters fly: the drift happens INSIDE the padded title box, so the hitbox doesn't move — pad generously or the effect self-cancels at the edges (same jitter class as demo 14's magnet bug).
- Per-letter spans kill kerning and ligatures — acceptable for spaced display caps like this; wrong for a script face where connections would visibly break at rest.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 113+ | 111+ |
Individual transform properties (translate/rotate) are Chrome 104+/Safari 14.1+/Firefox 72+; everything else is ancient. Floor reflects oklch() tokens.