34 CSS Floating Buttons26 / 34
Pulse Ring Attention-Getter Floating Button CSS
Three expanding rings on a stagger, the pattern used for call buttons, live indicators and onboarding spotlights. The important part is restraint: this demo shows the correct version (a short burst that stops), the radar variant, and a live-status dot — plus exactly why an infinite pulse on a persistent control is a mistake and when it is genuinely justified.
Published Updated
The code
<section class="fb-26" aria-label="Pulse ring attention getter button demo">
<div class="fb-26__stage">
<p class="fb-26__eyebrow">staggered rings · opacity leads scale</p>
<h2 class="fb-26__title">Pulse with a reason</h2>
<p class="fb-26__sub">Three rings, each a third of a cycle behind the last. Fade to zero before the scale ends, or the ring pops instead of dissolving.</p>
<div class="fb-26__row">
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i>
<button class="fb-26__fab" type="button" aria-label="Start a call">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6.5 3.8l2.6 1 1 3.1-1.8 1.6a11 11 0 0 0 5.2 5.2l1.6-1.8 3.1 1 1 2.6-1.6 1.9c-.8.9-2.1 1-3.2.5A17.6 17.6 0 0 1 4.4 8.6c-.5-1.1-.4-2.4.5-3.2z" fill="currentColor"/></svg>
</button>
</span>
<span class="fb-26__cap">live · infinite</span>
</div>
<div class="fb-26__cell">
<button class="fb-26__fab fb-26__fab--shadow" type="button" aria-label="Highlighted action, three pulses">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="currentColor"/></svg>
</button>
<span class="fb-26__cap">onboarding · 3×</span>
</div>
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__radar" aria-hidden="true"></i>
<button class="fb-26__fab fb-26__fab--radar" type="button" aria-label="Scanning for devices">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="2.6" fill="currentColor"/><path d="M7.8 16.2a6 6 0 0 1 0-8.4M16.2 7.8a6 6 0 0 1 0 8.4M5 19a10 10 0 0 1 0-14M19 5a10 10 0 0 1 0 14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</button>
</span>
<span class="fb-26__cap">radar sweep</span>
</div>
</div>
<p class="fb-26__chip">2.4s cycle · delays 0 / .8 / 1.6s · stops under reduced motion</p>
</div>
</section><section class="fb-26" aria-label="Pulse ring attention getter button demo">
<div class="fb-26__stage">
<p class="fb-26__eyebrow">staggered rings · opacity leads scale</p>
<h2 class="fb-26__title">Pulse with a reason</h2>
<p class="fb-26__sub">Three rings, each a third of a cycle behind the last. Fade to zero before the scale ends, or the ring pops instead of dissolving.</p>
<div class="fb-26__row">
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i>
<button class="fb-26__fab" type="button" aria-label="Start a call">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6.5 3.8l2.6 1 1 3.1-1.8 1.6a11 11 0 0 0 5.2 5.2l1.6-1.8 3.1 1 1 2.6-1.6 1.9c-.8.9-2.1 1-3.2.5A17.6 17.6 0 0 1 4.4 8.6c-.5-1.1-.4-2.4.5-3.2z" fill="currentColor"/></svg>
</button>
</span>
<span class="fb-26__cap">live · infinite</span>
</div>
<div class="fb-26__cell">
<button class="fb-26__fab fb-26__fab--shadow" type="button" aria-label="Highlighted action, three pulses">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="currentColor"/></svg>
</button>
<span class="fb-26__cap">onboarding · 3×</span>
</div>
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__radar" aria-hidden="true"></i>
<button class="fb-26__fab fb-26__fab--radar" type="button" aria-label="Scanning for devices">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="2.6" fill="currentColor"/><path d="M7.8 16.2a6 6 0 0 1 0-8.4M16.2 7.8a6 6 0 0 1 0 8.4M5 19a10 10 0 0 1 0-14M19 5a10 10 0 0 1 0 14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</button>
</span>
<span class="fb-26__cap">radar sweep</span>
</div>
</div>
<p class="fb-26__chip">2.4s cycle · delays 0 / .8 / 1.6s · stops under reduced motion</p>
</div>
</section>.fb-26 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-26-bg);
--fb-26-bg: radial-gradient(100% 80% at 50% 0%,oklch(0.26 0.05 160),oklch(0.15 0.02 175) 70%);
--fb-26-accent: oklch(0.78 0.17 155);
--fb-26-ink: oklch(0.97 0.01 160);
--fb-26-mut: oklch(0.76 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-26-ink);
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
}
.fb-26 *,
.fb-26 *::before,
.fb-26 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-26__stage {
width: min(100%,58ch);
display: grid;
gap: clamp(20px,3.4vw,32px);
justify-items: center;
text-align: center;
}
.fb-26__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-26-accent);
}
.fb-26__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-26__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-26-mut);
text-wrap: pretty;
}
.fb-26__row {
display: flex;
flex-wrap: wrap;
gap: clamp(28px,8vw,72px);
justify-content: center;
padding: 18px 0;
}
.fb-26__cell {
display: grid;
gap: 16px;
justify-items: center;
}
.fb-26__cap {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .06em;
color: var(--fb-26-mut);
}
.fb-26__wrap {
position: relative;
display: grid;
place-items: center;
}
.fb-26__ring {
position: absolute;
width: 66px;
height: 66px;
border-radius: 50%;
border: 2px solid var(--fb-26-accent);
pointer-events: none;
opacity: 0;
animation: fb-26-ping 2.4s cubic-bezier(0,.55,.45,1) infinite;
}
.fb-26__ring:nth-child(2) {
animation-delay: .8s;
}
.fb-26__ring:nth-child(3) {
animation-delay: 1.6s;
}
@keyframes fb-26-ping {
0% {
scale: .85;
opacity: .7;
}
70% {
opacity: 0;
}
100% {
scale: 2.2;
opacity: 0;
}
}
.fb-26__fab {
position: relative;
display: grid;
place-items: center;
inline-size: 66px;
block-size: 66px;
border: 0;
border-radius: 50%;
cursor: pointer;
color: oklch(0.15 0.04 160);
background: radial-gradient(120% 120% at 32% 26%,oklch(0.92 0.15 150),var(--fb-26-accent));
box-shadow: 0 16px 38px -14px oklch(0.78 0.17 155/.6),0 2px 6px oklch(0.08 0.01 175/.7);
transition: transform .28s cubic-bezier(.2,1.3,.4,1),box-shadow .3s ease;
}
.fb-26__fab svg {
width: 27px;
height: 27px;
}
.fb-26__fab:hover {
transform: translateY(-3px) scale(1.04);
}
.fb-26__fab:active {
transform: scale(.93);
}
.fb-26__fab:focus-visible {
outline: 3px solid oklch(0.98 0.01 160);
outline-offset: 6px;
}
.fb-26__fab--shadow {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.9 0.14 95),oklch(0.78 0.16 78));
color: oklch(0.2 0.05 78);
animation: fb-26-shadow 2.2s cubic-bezier(0,.55,.45,1) 3;
}
@keyframes fb-26-shadow {
0% {
box-shadow: 0 0 0 0 oklch(0.78 0.16 78/.65),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
70%,
100% {
box-shadow: 0 0 0 22px oklch(0.78 0.16 78/0),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
}
.fb-26__fab--radar {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.85 0.13 200),oklch(0.66 0.15 210));
color: oklch(0.14 0.04 210);
}
.fb-26__radar {
position: absolute;
width: 132px;
height: 132px;
border-radius: 50%;
pointer-events: none;
background: conic-gradient(from 0deg,transparent 0deg,color-mix(in oklch,oklch(0.7 0.15 205) 55%,transparent) 42deg,transparent 60deg);
-webkit-mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
animation: fb-26-sweep 3.4s linear infinite;
}
@keyframes fb-26-sweep {
to {
rotate: 360deg;
}
}
.fb-26__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-26-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
}
@media (prefers-reduced-motion: reduce) {
.fb-26__ring,
.fb-26__radar,
.fb-26__fab--shadow {
animation: none !important;
}
.fb-26__ring:first-of-type {
opacity: .35;
scale: 1.35;
}
}.fb-26 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-26-bg);
--fb-26-bg: radial-gradient(100% 80% at 50% 0%,oklch(0.26 0.05 160),oklch(0.15 0.02 175) 70%);
--fb-26-accent: oklch(0.78 0.17 155);
--fb-26-ink: oklch(0.97 0.01 160);
--fb-26-mut: oklch(0.76 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-26-ink);
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
}
.fb-26 *,
.fb-26 *::before,
.fb-26 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-26__stage {
width: min(100%,58ch);
display: grid;
gap: clamp(20px,3.4vw,32px);
justify-items: center;
text-align: center;
}
.fb-26__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-26-accent);
}
.fb-26__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-26__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-26-mut);
text-wrap: pretty;
}
.fb-26__row {
display: flex;
flex-wrap: wrap;
gap: clamp(28px,8vw,72px);
justify-content: center;
padding: 18px 0;
}
.fb-26__cell {
display: grid;
gap: 16px;
justify-items: center;
}
.fb-26__cap {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .06em;
color: var(--fb-26-mut);
}
.fb-26__wrap {
position: relative;
display: grid;
place-items: center;
}
.fb-26__ring {
position: absolute;
width: 66px;
height: 66px;
border-radius: 50%;
border: 2px solid var(--fb-26-accent);
pointer-events: none;
opacity: 0;
animation: fb-26-ping 2.4s cubic-bezier(0,.55,.45,1) infinite;
}
.fb-26__ring:nth-child(2) {
animation-delay: .8s;
}
.fb-26__ring:nth-child(3) {
animation-delay: 1.6s;
}
@keyframes fb-26-ping {
0% {
scale: .85;
opacity: .7;
}
70% {
opacity: 0;
}
100% {
scale: 2.2;
opacity: 0;
}
}
.fb-26__fab {
position: relative;
display: grid;
place-items: center;
inline-size: 66px;
block-size: 66px;
border: 0;
border-radius: 50%;
cursor: pointer;
color: oklch(0.15 0.04 160);
background: radial-gradient(120% 120% at 32% 26%,oklch(0.92 0.15 150),var(--fb-26-accent));
box-shadow: 0 16px 38px -14px oklch(0.78 0.17 155/.6),0 2px 6px oklch(0.08 0.01 175/.7);
transition: transform .28s cubic-bezier(.2,1.3,.4,1),box-shadow .3s ease;
}
.fb-26__fab svg {
width: 27px;
height: 27px;
}
.fb-26__fab:hover {
transform: translateY(-3px) scale(1.04);
}
.fb-26__fab:active {
transform: scale(.93);
}
.fb-26__fab:focus-visible {
outline: 3px solid oklch(0.98 0.01 160);
outline-offset: 6px;
}
.fb-26__fab--shadow {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.9 0.14 95),oklch(0.78 0.16 78));
color: oklch(0.2 0.05 78);
animation: fb-26-shadow 2.2s cubic-bezier(0,.55,.45,1) 3;
}
@keyframes fb-26-shadow {
0% {
box-shadow: 0 0 0 0 oklch(0.78 0.16 78/.65),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
70%,
100% {
box-shadow: 0 0 0 22px oklch(0.78 0.16 78/0),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
}
.fb-26__fab--radar {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.85 0.13 200),oklch(0.66 0.15 210));
color: oklch(0.14 0.04 210);
}
.fb-26__radar {
position: absolute;
width: 132px;
height: 132px;
border-radius: 50%;
pointer-events: none;
background: conic-gradient(from 0deg,transparent 0deg,color-mix(in oklch,oklch(0.7 0.15 205) 55%,transparent) 42deg,transparent 60deg);
-webkit-mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
animation: fb-26-sweep 3.4s linear infinite;
}
@keyframes fb-26-sweep {
to {
rotate: 360deg;
}
}
.fb-26__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-26-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
}
@media (prefers-reduced-motion: reduce) {
.fb-26__ring,
.fb-26__radar,
.fb-26__fab--shadow {
animation: none !important;
}
.fb-26__ring:first-of-type {
opacity: .35;
scale: 1.35;
}
}Here's a working CSS Floating Button 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: Pulse Ring Attention-Getter Floating Button CSS
Source: https://codefronts.com/components/css-floating-buttons/fb03/
Three expanding rings on a stagger, the pattern used for call buttons, live indicators and onboarding spotlights. The important part is restraint: this demo shows the correct version (a short burst that stops), the radar variant, and a live-status dot — plus exactly why an infinite pulse on a persistent control is a mistake and when it is genuinely justified.
## HTML
```html
<section class="fb-26" aria-label="Pulse ring attention getter button demo">
<div class="fb-26__stage">
<p class="fb-26__eyebrow">staggered rings · opacity leads scale</p>
<h2 class="fb-26__title">Pulse with a reason</h2>
<p class="fb-26__sub">Three rings, each a third of a cycle behind the last. Fade to zero before the scale ends, or the ring pops instead of dissolving.</p>
<div class="fb-26__row">
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i>
<button class="fb-26__fab" type="button" aria-label="Start a call">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6.5 3.8l2.6 1 1 3.1-1.8 1.6a11 11 0 0 0 5.2 5.2l1.6-1.8 3.1 1 1 2.6-1.6 1.9c-.8.9-2.1 1-3.2.5A17.6 17.6 0 0 1 4.4 8.6c-.5-1.1-.4-2.4.5-3.2z" fill="currentColor"/></svg>
</button>
</span>
<span class="fb-26__cap">live · infinite</span>
</div>
<div class="fb-26__cell">
<button class="fb-26__fab fb-26__fab--shadow" type="button" aria-label="Highlighted action, three pulses">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="currentColor"/></svg>
</button>
<span class="fb-26__cap">onboarding · 3×</span>
</div>
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__radar" aria-hidden="true"></i>
<button class="fb-26__fab fb-26__fab--radar" type="button" aria-label="Scanning for devices">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="2.6" fill="currentColor"/><path d="M7.8 16.2a6 6 0 0 1 0-8.4M16.2 7.8a6 6 0 0 1 0 8.4M5 19a10 10 0 0 1 0-14M19 5a10 10 0 0 1 0 14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</button>
</span>
<span class="fb-26__cap">radar sweep</span>
</div>
</div>
<p class="fb-26__chip">2.4s cycle · delays 0 / .8 / 1.6s · stops under reduced motion</p>
</div>
</section>
```
## CSS
```css
.fb-26 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-26-bg);
--fb-26-bg: radial-gradient(100% 80% at 50% 0%,oklch(0.26 0.05 160),oklch(0.15 0.02 175) 70%);
--fb-26-accent: oklch(0.78 0.17 155);
--fb-26-ink: oklch(0.97 0.01 160);
--fb-26-mut: oklch(0.76 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-26-ink);
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
}
.fb-26 *,
.fb-26 *::before,
.fb-26 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-26__stage {
width: min(100%,58ch);
display: grid;
gap: clamp(20px,3.4vw,32px);
justify-items: center;
text-align: center;
}
.fb-26__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-26-accent);
}
.fb-26__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-26__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-26-mut);
text-wrap: pretty;
}
.fb-26__row {
display: flex;
flex-wrap: wrap;
gap: clamp(28px,8vw,72px);
justify-content: center;
padding: 18px 0;
}
.fb-26__cell {
display: grid;
gap: 16px;
justify-items: center;
}
.fb-26__cap {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .06em;
color: var(--fb-26-mut);
}
.fb-26__wrap {
position: relative;
display: grid;
place-items: center;
}
.fb-26__ring {
position: absolute;
width: 66px;
height: 66px;
border-radius: 50%;
border: 2px solid var(--fb-26-accent);
pointer-events: none;
opacity: 0;
animation: fb-26-ping 2.4s cubic-bezier(0,.55,.45,1) infinite;
}
.fb-26__ring:nth-child(2) {
animation-delay: .8s;
}
.fb-26__ring:nth-child(3) {
animation-delay: 1.6s;
}
@keyframes fb-26-ping {
0% {
scale: .85;
opacity: .7;
}
70% {
opacity: 0;
}
100% {
scale: 2.2;
opacity: 0;
}
}
.fb-26__fab {
position: relative;
display: grid;
place-items: center;
inline-size: 66px;
block-size: 66px;
border: 0;
border-radius: 50%;
cursor: pointer;
color: oklch(0.15 0.04 160);
background: radial-gradient(120% 120% at 32% 26%,oklch(0.92 0.15 150),var(--fb-26-accent));
box-shadow: 0 16px 38px -14px oklch(0.78 0.17 155/.6),0 2px 6px oklch(0.08 0.01 175/.7);
transition: transform .28s cubic-bezier(.2,1.3,.4,1),box-shadow .3s ease;
}
.fb-26__fab svg {
width: 27px;
height: 27px;
}
.fb-26__fab:hover {
transform: translateY(-3px) scale(1.04);
}
.fb-26__fab:active {
transform: scale(.93);
}
.fb-26__fab:focus-visible {
outline: 3px solid oklch(0.98 0.01 160);
outline-offset: 6px;
}
.fb-26__fab--shadow {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.9 0.14 95),oklch(0.78 0.16 78));
color: oklch(0.2 0.05 78);
animation: fb-26-shadow 2.2s cubic-bezier(0,.55,.45,1) 3;
}
@keyframes fb-26-shadow {
0% {
box-shadow: 0 0 0 0 oklch(0.78 0.16 78/.65),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
70%,
100% {
box-shadow: 0 0 0 22px oklch(0.78 0.16 78/0),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
}
.fb-26__fab--radar {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.85 0.13 200),oklch(0.66 0.15 210));
color: oklch(0.14 0.04 210);
}
.fb-26__radar {
position: absolute;
width: 132px;
height: 132px;
border-radius: 50%;
pointer-events: none;
background: conic-gradient(from 0deg,transparent 0deg,color-mix(in oklch,oklch(0.7 0.15 205) 55%,transparent) 42deg,transparent 60deg);
-webkit-mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
animation: fb-26-sweep 3.4s linear infinite;
}
@keyframes fb-26-sweep {
to {
rotate: 360deg;
}
}
.fb-26__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-26-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
}
@media (prefers-reduced-motion: reduce) {
.fb-26__ring,
.fb-26__radar,
.fb-26__fab--shadow {
animation: none !important;
}
.fb-26__ring:first-of-type {
opacity: .35;
scale: 1.35;
}
}
```Here's a working CSS Floating Button 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: Pulse Ring Attention-Getter Floating Button CSS
Source: https://codefronts.com/components/css-floating-buttons/fb03/
Three expanding rings on a stagger, the pattern used for call buttons, live indicators and onboarding spotlights. The important part is restraint: this demo shows the correct version (a short burst that stops), the radar variant, and a live-status dot — plus exactly why an infinite pulse on a persistent control is a mistake and when it is genuinely justified.
## HTML
```html
<section class="fb-26" aria-label="Pulse ring attention getter button demo">
<div class="fb-26__stage">
<p class="fb-26__eyebrow">staggered rings · opacity leads scale</p>
<h2 class="fb-26__title">Pulse with a reason</h2>
<p class="fb-26__sub">Three rings, each a third of a cycle behind the last. Fade to zero before the scale ends, or the ring pops instead of dissolving.</p>
<div class="fb-26__row">
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i><i class="fb-26__ring" aria-hidden="true"></i>
<button class="fb-26__fab" type="button" aria-label="Start a call">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6.5 3.8l2.6 1 1 3.1-1.8 1.6a11 11 0 0 0 5.2 5.2l1.6-1.8 3.1 1 1 2.6-1.6 1.9c-.8.9-2.1 1-3.2.5A17.6 17.6 0 0 1 4.4 8.6c-.5-1.1-.4-2.4.5-3.2z" fill="currentColor"/></svg>
</button>
</span>
<span class="fb-26__cap">live · infinite</span>
</div>
<div class="fb-26__cell">
<button class="fb-26__fab fb-26__fab--shadow" type="button" aria-label="Highlighted action, three pulses">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="currentColor"/></svg>
</button>
<span class="fb-26__cap">onboarding · 3×</span>
</div>
<div class="fb-26__cell">
<span class="fb-26__wrap">
<i class="fb-26__radar" aria-hidden="true"></i>
<button class="fb-26__fab fb-26__fab--radar" type="button" aria-label="Scanning for devices">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="2.6" fill="currentColor"/><path d="M7.8 16.2a6 6 0 0 1 0-8.4M16.2 7.8a6 6 0 0 1 0 8.4M5 19a10 10 0 0 1 0-14M19 5a10 10 0 0 1 0 14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</button>
</span>
<span class="fb-26__cap">radar sweep</span>
</div>
</div>
<p class="fb-26__chip">2.4s cycle · delays 0 / .8 / 1.6s · stops under reduced motion</p>
</div>
</section>
```
## CSS
```css
.fb-26 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-26-bg);
--fb-26-bg: radial-gradient(100% 80% at 50% 0%,oklch(0.26 0.05 160),oklch(0.15 0.02 175) 70%);
--fb-26-accent: oklch(0.78 0.17 155);
--fb-26-ink: oklch(0.97 0.01 160);
--fb-26-mut: oklch(0.76 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-26-ink);
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
}
.fb-26 *,
.fb-26 *::before,
.fb-26 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-26__stage {
width: min(100%,58ch);
display: grid;
gap: clamp(20px,3.4vw,32px);
justify-items: center;
text-align: center;
}
.fb-26__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-26-accent);
}
.fb-26__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-26__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-26-mut);
text-wrap: pretty;
}
.fb-26__row {
display: flex;
flex-wrap: wrap;
gap: clamp(28px,8vw,72px);
justify-content: center;
padding: 18px 0;
}
.fb-26__cell {
display: grid;
gap: 16px;
justify-items: center;
}
.fb-26__cap {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .06em;
color: var(--fb-26-mut);
}
.fb-26__wrap {
position: relative;
display: grid;
place-items: center;
}
.fb-26__ring {
position: absolute;
width: 66px;
height: 66px;
border-radius: 50%;
border: 2px solid var(--fb-26-accent);
pointer-events: none;
opacity: 0;
animation: fb-26-ping 2.4s cubic-bezier(0,.55,.45,1) infinite;
}
.fb-26__ring:nth-child(2) {
animation-delay: .8s;
}
.fb-26__ring:nth-child(3) {
animation-delay: 1.6s;
}
@keyframes fb-26-ping {
0% {
scale: .85;
opacity: .7;
}
70% {
opacity: 0;
}
100% {
scale: 2.2;
opacity: 0;
}
}
.fb-26__fab {
position: relative;
display: grid;
place-items: center;
inline-size: 66px;
block-size: 66px;
border: 0;
border-radius: 50%;
cursor: pointer;
color: oklch(0.15 0.04 160);
background: radial-gradient(120% 120% at 32% 26%,oklch(0.92 0.15 150),var(--fb-26-accent));
box-shadow: 0 16px 38px -14px oklch(0.78 0.17 155/.6),0 2px 6px oklch(0.08 0.01 175/.7);
transition: transform .28s cubic-bezier(.2,1.3,.4,1),box-shadow .3s ease;
}
.fb-26__fab svg {
width: 27px;
height: 27px;
}
.fb-26__fab:hover {
transform: translateY(-3px) scale(1.04);
}
.fb-26__fab:active {
transform: scale(.93);
}
.fb-26__fab:focus-visible {
outline: 3px solid oklch(0.98 0.01 160);
outline-offset: 6px;
}
.fb-26__fab--shadow {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.9 0.14 95),oklch(0.78 0.16 78));
color: oklch(0.2 0.05 78);
animation: fb-26-shadow 2.2s cubic-bezier(0,.55,.45,1) 3;
}
@keyframes fb-26-shadow {
0% {
box-shadow: 0 0 0 0 oklch(0.78 0.16 78/.65),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
70%,
100% {
box-shadow: 0 0 0 22px oklch(0.78 0.16 78/0),0 16px 38px -14px oklch(0.78 0.16 78/.6);
}
}
.fb-26__fab--radar {
background: radial-gradient(120% 120% at 32% 26%,oklch(0.85 0.13 200),oklch(0.66 0.15 210));
color: oklch(0.14 0.04 210);
}
.fb-26__radar {
position: absolute;
width: 132px;
height: 132px;
border-radius: 50%;
pointer-events: none;
background: conic-gradient(from 0deg,transparent 0deg,color-mix(in oklch,oklch(0.7 0.15 205) 55%,transparent) 42deg,transparent 60deg);
-webkit-mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
mask: radial-gradient(circle,transparent 32px,#000 33px,#000 66px,transparent 67px);
animation: fb-26-sweep 3.4s linear infinite;
}
@keyframes fb-26-sweep {
to {
rotate: 360deg;
}
}
.fb-26__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-26-mut);
padding: 8px 14px;
border: 1px solid oklch(1 0 0/.16);
border-radius: 99px;
}
@media (prefers-reduced-motion: reduce) {
.fb-26__ring,
.fb-26__radar,
.fb-26__fab--shadow {
animation: none !important;
}
.fb-26__ring:first-of-type {
opacity: .35;
scale: 1.35;
}
}
```How this works
Rings are pseudo-elements scaling out with a fading opacity, each offset by a third of the cycle:
@keyframes ping{ 0%{ scale:.85; opacity:.7 } 70%{ opacity:0 } 100%{ scale:2.1; opacity:0 } }
.ring { animation:ping 2.4s cubic-bezier(0,.55,.45,1) infinite }
.ring:nth-child(2){ animation-delay:.8s }
.ring:nth-child(3){ animation-delay:1.6s }Opacity must reach zero before the scale finishes (70% here), otherwise the ring pops out of existence at full size. That single offset is what makes the falloff read as a wave rather than a flicker.
Two cheaper variants are included. The box-shadow pulse animates a spread-only shadow from 0 to 18px — one element, no extra DOM, but it cannot fade independently of the button. The radar sweep is a conic-gradient rotating behind a masked circle, for "scanning" rather than "attention".
On restraint: an infinite pulse is defensible for a live state (recording, an active call, a device pairing) because the animation is the status. For a plain create button it is nagging — use three iterations on first render and stop.
Make it yours
- Iteration count:
animation-iteration-count:3for onboarding,infiniteonly for genuine live status. - Ring count: two rings read calm, three read urgent, four read broken. Space delays evenly across the duration.
- Colour fade: animate the border colour from the accent to transparent alongside opacity for a softer edge.
- Speed: 2.4s is a heartbeat. Under 1.2s it reads as an alarm — appropriate for an emergency call button, wrong for anything else.
Gotchas — read before shipping
- Rings must be pointer-events:none or the expanded ring intercepts clicks well outside the button's visual bounds.
- Animating box-shadow spread repaints on every frame; animating scale on a pseudo-element stays on the compositor. Prefer the latter for persistent pulses.
- Infinite motion near text is a known trigger for vestibular discomfort. Always provide the reduced-motion stop, and consider a static ring instead.
- A pulse plus a badge plus a colour change is three attention signals fighting each other. Pick one.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
Keyframes, scale and conic-gradient (Chrome 69+, Safari 12.1+, Firefox 83+) are all broadly supported. oklch() tokens are the only modern floor.