03 / 34
All 34 in 34 CSS Floating Buttons
- 01Material Design 3 Floating Action Button (FAB)
- 02iOS-Style Compose Floating Button
- 03WhatsApp Floating Message Button CSS
- 04Intercom / Drift Chat Bubble Floating Button
- 05Accessible WAI-ARIA Floating Action Button (WCAG 2.2)
- 06Extended FAB with Icon and Label (Material Design 3)
- 07Speed Dial Radial Menu FAB with Backdrop
- 08Pure CSS FAB Speed Dial (No JavaScript)
- 09Material Gradient Floating Action Button CSS
- 10Vertical and Horizontal Speed Dial FAB Menu CSS
- 11Morphing FAB Button That Expands to Pill CSS
- 12Scroll-to-Top Floating Button with SVG Progress Ring
- 13Floating Chat Widget FAB with Notification Panel
- 14Fan and Arc Radial Spread FAB Menu CSS
- 15Apple Squircle Superellipse Floating Button CSS
- 16Social Share Floating Button Cluster CSS
- 17Glassmorphism Floating Action Button with Backdrop Blur
- 18Cyberpunk Neon Glow Floating Button CSS
- 19Floating Compose Button with Tooltip on Hover CSS
- 20Draggable Floating Action Button with Corner Snap CSS
- 21Notification Bell Floating Button with Badge Counter
- 22Floating Cart Button with Live Badge Counter CSS
- 23Magnetic Cursor Follower Floating Button CSS
- 24Dark Mode Theme Toggle Floating Button CSS
- 25Classic Gradient Circle Plus FAB CSS
- 26Pulse Ring Attention-Getter Floating Button CSS
- 27Pill-Shaped FAB with Icon and Text Label CSS
- 28Neo-Brutalist Hard Shadow Floating Button CSS
- 29Floating Button with Loading Spinner State CSS
- 30Draggable Floating Button with 6-Dot Grip Handle CSS
- 31Voice Recording Microphone Floating Button CSS
- 32Aurora Gradient Drift Premium Floating Button CSS
- 33Quick Emoji Reply Reaction Floating Button CSS
- 34Modern Squircle Rounded Square FAB CSS
WhatsApp Floating Message Button CSS
The green "message us" button every small-business site puts in the bottom-right corner, done properly: a resting circle that expands into a labelled pill on hover and focus, an attention pulse that runs three times and then stops, and an online/away status dot. The label is real text inside the button, so it is announced, translatable and searchable — not a tooltip bolted on with a data-attribute.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<section class="fb-03" aria-label="Floating message button demo">
<div class="fb-03__page">
<div class="fb-03__hero">
<p class="fb-03__eyebrow">grid-template-columns 0fr → 1fr</p>
<h2 class="fb-03__title">A circle that becomes a sentence</h2>
<p class="fb-03__sub">Hover or tab to the button. The label is real text inside the same element, so the accessible name never changes as it expands.</p>
<p class="fb-03__chip">3-cycle pulse · intrinsic width · status dot</p>
</div>
<div class="fb-03__cards" aria-hidden="true">
<div class="fb-03__card"></div><div class="fb-03__card"></div><div class="fb-03__card"></div>
</div>
</div>
<button class="fb-03__msg" type="button">
<span class="fb-03__ico">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5.6A2.4 2.4 0 0 1 6.4 3.2h11.2A2.4 2.4 0 0 1 20 5.6v7.2a2.4 2.4 0 0 1-2.4 2.4h-6.24l-4.02 3.2A.6.6 0 0 1 6.4 17.9v-2.7H6.4A2.4 2.4 0 0 1 4 12.8V5.6z" fill="currentColor"/><circle cx="9" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="12" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="15" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/></svg>
<span class="fb-03__status" aria-hidden="true"></span>
</span>
<span class="fb-03__label"><b>Message us</b><i>Typically replies in 5 min</i></span>
</button>
</section><section class="fb-03" aria-label="Floating message button demo">
<div class="fb-03__page">
<div class="fb-03__hero">
<p class="fb-03__eyebrow">grid-template-columns 0fr → 1fr</p>
<h2 class="fb-03__title">A circle that becomes a sentence</h2>
<p class="fb-03__sub">Hover or tab to the button. The label is real text inside the same element, so the accessible name never changes as it expands.</p>
<p class="fb-03__chip">3-cycle pulse · intrinsic width · status dot</p>
</div>
<div class="fb-03__cards" aria-hidden="true">
<div class="fb-03__card"></div><div class="fb-03__card"></div><div class="fb-03__card"></div>
</div>
</div>
<button class="fb-03__msg" type="button">
<span class="fb-03__ico">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5.6A2.4 2.4 0 0 1 6.4 3.2h11.2A2.4 2.4 0 0 1 20 5.6v7.2a2.4 2.4 0 0 1-2.4 2.4h-6.24l-4.02 3.2A.6.6 0 0 1 6.4 17.9v-2.7H6.4A2.4 2.4 0 0 1 4 12.8V5.6z" fill="currentColor"/><circle cx="9" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="12" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="15" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/></svg>
<span class="fb-03__status" aria-hidden="true"></span>
</span>
<span class="fb-03__label"><b>Message us</b><i>Typically replies in 5 min</i></span>
</button>
</section>.fb-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-03-bg);
--fb-03-bg: oklch(0.97 0.012 150);
--fb-03-brand: oklch(0.62 0.16 152);
--fb-03-brandDeep: oklch(0.5 0.14 152);
--fb-03-ink: oklch(0.24 0.03 160);
--fb-03-mut: oklch(0.54 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-03-ink);
position: relative;
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
overflow: hidden;
}
.fb-03 *,
.fb-03 *::before,
.fb-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-03__page {
width: min(100%,660px);
display: grid;
gap: clamp(20px,4vw,34px);
justify-items: center;
text-align: center;
}
.fb-03__hero {
display: grid;
gap: 11px;
justify-items: center;
}
.fb-03__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-03-brandDeep);
}
.fb-03__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-03__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-03-mut);
text-wrap: pretty;
}
.fb-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-03-mut);
padding: 8px 14px;
border: 1px solid oklch(0.24 0.03 160/.14);
border-radius: 99px;
}
.fb-03__cards {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 14px;
width: 100%;
}
.fb-03__card {
height: clamp(72px,12vw,104px);
border-radius: 16px;
background: oklch(1 0 0);
border: 1px solid oklch(0.24 0.03 160/.09);
box-shadow: 0 10px 26px -20px oklch(0.24 0.03 160/.6);
}
.fb-03__card:nth-child(2) {
background: linear-gradient(120deg,oklch(0.93 0.04 152),oklch(0.98 0.01 152));
}
.fb-03__msg {
position: absolute;
right: clamp(16px,3vw,30px);
bottom: calc(clamp(16px,3vw,30px) + env(safe-area-inset-bottom));
z-index: 5;
display: grid;
grid-template-columns: auto 0fr;
align-items: center;
gap: 0;
min-height: 60px;
padding: 8px;
border: 0;
border-radius: 99px;
background: linear-gradient(180deg,var(--fb-03-brand),var(--fb-03-brandDeep));
color: oklch(0.99 0.01 152);
cursor: pointer;
text-align: left;
box-shadow: 0 8px 26px -8px oklch(0.5 0.14 152/.7),0 2px 6px oklch(0.3 0.06 152/.25);
transition: grid-template-columns .34s cubic-bezier(.2,.8,.2,1),gap .34s cubic-bezier(.2,.8,.2,1),box-shadow .3s ease,transform .2s ease;
}
.fb-03__msg:is(:hover,:focus-visible) {
grid-template-columns: auto 1fr;
gap: 12px;
box-shadow: 0 14px 34px -8px oklch(0.5 0.14 152/.75);
}
.fb-03__msg:focus-visible {
outline: 3px solid var(--fb-03-brandDeep);
outline-offset: 3px;
}
.fb-03__msg:active {
transform: scale(.97);
}
.fb-03__ico {
position: relative;
flex: none;
width: 44px;
height: 44px;
border-radius: 50%;
display: grid;
place-items: center;
background: oklch(1 0 0/.16);
}
.fb-03__ico svg {
width: 26px;
height: 26px;
}
.fb-03__ico::after {
content: "";
position: absolute;
inset: -9px;
border-radius: 50%;
border: 2px solid oklch(1 0 0/.55);
animation: fb-03-pulse 2.1s ease-out 3;
pointer-events: none;
}
.fb-03__msg:hover .fb-03__ico::after {
animation: fb-03-pulse 2.1s ease-out 3;
}
@keyframes fb-03-pulse {
0% {
transform: scale(.86);
opacity: .8;
}
70% {
transform: scale(1.35);
opacity: 0;
}
100% {
transform: scale(1.35);
opacity: 0;
}
}
.fb-03__status {
position: absolute;
right: 1px;
bottom: 1px;
width: 11px;
height: 11px;
border-radius: 50%;
background: oklch(0.85 0.19 130);
box-shadow: 0 0 0 2.5px var(--fb-03-brandDeep);
}
.fb-03__label {
overflow: hidden;
white-space: nowrap;
display: grid;
gap: 1px;
min-width: 0;
}
.fb-03__msg:is(:hover,:focus-visible) .fb-03__label {
padding-right: 6px;
}
.fb-03__label b {
font-size: 15px;
font-weight: 650;
letter-spacing: -.01em;
}
.fb-03__label i {
font-style: normal;
font-size: 11.5px;
color: oklch(0.97 0.02 152/.82);
}
@media (prefers-reduced-motion: reduce) {
.fb-03__msg {
transition: none;
}
.fb-03__ico::after {
animation: none;
opacity: 0;
}
}.fb-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-03-bg);
--fb-03-bg: oklch(0.97 0.012 150);
--fb-03-brand: oklch(0.62 0.16 152);
--fb-03-brandDeep: oklch(0.5 0.14 152);
--fb-03-ink: oklch(0.24 0.03 160);
--fb-03-mut: oklch(0.54 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-03-ink);
position: relative;
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
overflow: hidden;
}
.fb-03 *,
.fb-03 *::before,
.fb-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-03__page {
width: min(100%,660px);
display: grid;
gap: clamp(20px,4vw,34px);
justify-items: center;
text-align: center;
}
.fb-03__hero {
display: grid;
gap: 11px;
justify-items: center;
}
.fb-03__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-03-brandDeep);
}
.fb-03__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-03__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-03-mut);
text-wrap: pretty;
}
.fb-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-03-mut);
padding: 8px 14px;
border: 1px solid oklch(0.24 0.03 160/.14);
border-radius: 99px;
}
.fb-03__cards {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 14px;
width: 100%;
}
.fb-03__card {
height: clamp(72px,12vw,104px);
border-radius: 16px;
background: oklch(1 0 0);
border: 1px solid oklch(0.24 0.03 160/.09);
box-shadow: 0 10px 26px -20px oklch(0.24 0.03 160/.6);
}
.fb-03__card:nth-child(2) {
background: linear-gradient(120deg,oklch(0.93 0.04 152),oklch(0.98 0.01 152));
}
.fb-03__msg {
position: absolute;
right: clamp(16px,3vw,30px);
bottom: calc(clamp(16px,3vw,30px) + env(safe-area-inset-bottom));
z-index: 5;
display: grid;
grid-template-columns: auto 0fr;
align-items: center;
gap: 0;
min-height: 60px;
padding: 8px;
border: 0;
border-radius: 99px;
background: linear-gradient(180deg,var(--fb-03-brand),var(--fb-03-brandDeep));
color: oklch(0.99 0.01 152);
cursor: pointer;
text-align: left;
box-shadow: 0 8px 26px -8px oklch(0.5 0.14 152/.7),0 2px 6px oklch(0.3 0.06 152/.25);
transition: grid-template-columns .34s cubic-bezier(.2,.8,.2,1),gap .34s cubic-bezier(.2,.8,.2,1),box-shadow .3s ease,transform .2s ease;
}
.fb-03__msg:is(:hover,:focus-visible) {
grid-template-columns: auto 1fr;
gap: 12px;
box-shadow: 0 14px 34px -8px oklch(0.5 0.14 152/.75);
}
.fb-03__msg:focus-visible {
outline: 3px solid var(--fb-03-brandDeep);
outline-offset: 3px;
}
.fb-03__msg:active {
transform: scale(.97);
}
.fb-03__ico {
position: relative;
flex: none;
width: 44px;
height: 44px;
border-radius: 50%;
display: grid;
place-items: center;
background: oklch(1 0 0/.16);
}
.fb-03__ico svg {
width: 26px;
height: 26px;
}
.fb-03__ico::after {
content: "";
position: absolute;
inset: -9px;
border-radius: 50%;
border: 2px solid oklch(1 0 0/.55);
animation: fb-03-pulse 2.1s ease-out 3;
pointer-events: none;
}
.fb-03__msg:hover .fb-03__ico::after {
animation: fb-03-pulse 2.1s ease-out 3;
}
@keyframes fb-03-pulse {
0% {
transform: scale(.86);
opacity: .8;
}
70% {
transform: scale(1.35);
opacity: 0;
}
100% {
transform: scale(1.35);
opacity: 0;
}
}
.fb-03__status {
position: absolute;
right: 1px;
bottom: 1px;
width: 11px;
height: 11px;
border-radius: 50%;
background: oklch(0.85 0.19 130);
box-shadow: 0 0 0 2.5px var(--fb-03-brandDeep);
}
.fb-03__label {
overflow: hidden;
white-space: nowrap;
display: grid;
gap: 1px;
min-width: 0;
}
.fb-03__msg:is(:hover,:focus-visible) .fb-03__label {
padding-right: 6px;
}
.fb-03__label b {
font-size: 15px;
font-weight: 650;
letter-spacing: -.01em;
}
.fb-03__label i {
font-style: normal;
font-size: 11.5px;
color: oklch(0.97 0.02 152/.82);
}
@media (prefers-reduced-motion: reduce) {
.fb-03__msg {
transition: none;
}
.fb-03__ico::after {
animation: none;
opacity: 0;
}
}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: WhatsApp Floating Message Button CSS
Source: https://codefronts.com/components/css-floating-buttons/whatsapp-floating-message-button-css/
The green "message us" button every small-business site puts in the bottom-right corner, done properly: a resting circle that expands into a labelled pill on hover and focus, an attention pulse that runs three times and then stops, and an online/away status dot. The label is real text inside the button, so it is announced, translatable and searchable — not a tooltip bolted on with a data-attribute.
## HTML
```html
<section class="fb-03" aria-label="Floating message button demo">
<div class="fb-03__page">
<div class="fb-03__hero">
<p class="fb-03__eyebrow">grid-template-columns 0fr → 1fr</p>
<h2 class="fb-03__title">A circle that becomes a sentence</h2>
<p class="fb-03__sub">Hover or tab to the button. The label is real text inside the same element, so the accessible name never changes as it expands.</p>
<p class="fb-03__chip">3-cycle pulse · intrinsic width · status dot</p>
</div>
<div class="fb-03__cards" aria-hidden="true">
<div class="fb-03__card"></div><div class="fb-03__card"></div><div class="fb-03__card"></div>
</div>
</div>
<button class="fb-03__msg" type="button">
<span class="fb-03__ico">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5.6A2.4 2.4 0 0 1 6.4 3.2h11.2A2.4 2.4 0 0 1 20 5.6v7.2a2.4 2.4 0 0 1-2.4 2.4h-6.24l-4.02 3.2A.6.6 0 0 1 6.4 17.9v-2.7H6.4A2.4 2.4 0 0 1 4 12.8V5.6z" fill="currentColor"/><circle cx="9" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="12" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="15" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/></svg>
<span class="fb-03__status" aria-hidden="true"></span>
</span>
<span class="fb-03__label"><b>Message us</b><i>Typically replies in 5 min</i></span>
</button>
</section>
```
## CSS
```css
.fb-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-03-bg);
--fb-03-bg: oklch(0.97 0.012 150);
--fb-03-brand: oklch(0.62 0.16 152);
--fb-03-brandDeep: oklch(0.5 0.14 152);
--fb-03-ink: oklch(0.24 0.03 160);
--fb-03-mut: oklch(0.54 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-03-ink);
position: relative;
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
overflow: hidden;
}
.fb-03 *,
.fb-03 *::before,
.fb-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-03__page {
width: min(100%,660px);
display: grid;
gap: clamp(20px,4vw,34px);
justify-items: center;
text-align: center;
}
.fb-03__hero {
display: grid;
gap: 11px;
justify-items: center;
}
.fb-03__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-03-brandDeep);
}
.fb-03__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-03__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-03-mut);
text-wrap: pretty;
}
.fb-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-03-mut);
padding: 8px 14px;
border: 1px solid oklch(0.24 0.03 160/.14);
border-radius: 99px;
}
.fb-03__cards {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 14px;
width: 100%;
}
.fb-03__card {
height: clamp(72px,12vw,104px);
border-radius: 16px;
background: oklch(1 0 0);
border: 1px solid oklch(0.24 0.03 160/.09);
box-shadow: 0 10px 26px -20px oklch(0.24 0.03 160/.6);
}
.fb-03__card:nth-child(2) {
background: linear-gradient(120deg,oklch(0.93 0.04 152),oklch(0.98 0.01 152));
}
.fb-03__msg {
position: absolute;
right: clamp(16px,3vw,30px);
bottom: calc(clamp(16px,3vw,30px) + env(safe-area-inset-bottom));
z-index: 5;
display: grid;
grid-template-columns: auto 0fr;
align-items: center;
gap: 0;
min-height: 60px;
padding: 8px;
border: 0;
border-radius: 99px;
background: linear-gradient(180deg,var(--fb-03-brand),var(--fb-03-brandDeep));
color: oklch(0.99 0.01 152);
cursor: pointer;
text-align: left;
box-shadow: 0 8px 26px -8px oklch(0.5 0.14 152/.7),0 2px 6px oklch(0.3 0.06 152/.25);
transition: grid-template-columns .34s cubic-bezier(.2,.8,.2,1),gap .34s cubic-bezier(.2,.8,.2,1),box-shadow .3s ease,transform .2s ease;
}
.fb-03__msg:is(:hover,:focus-visible) {
grid-template-columns: auto 1fr;
gap: 12px;
box-shadow: 0 14px 34px -8px oklch(0.5 0.14 152/.75);
}
.fb-03__msg:focus-visible {
outline: 3px solid var(--fb-03-brandDeep);
outline-offset: 3px;
}
.fb-03__msg:active {
transform: scale(.97);
}
.fb-03__ico {
position: relative;
flex: none;
width: 44px;
height: 44px;
border-radius: 50%;
display: grid;
place-items: center;
background: oklch(1 0 0/.16);
}
.fb-03__ico svg {
width: 26px;
height: 26px;
}
.fb-03__ico::after {
content: "";
position: absolute;
inset: -9px;
border-radius: 50%;
border: 2px solid oklch(1 0 0/.55);
animation: fb-03-pulse 2.1s ease-out 3;
pointer-events: none;
}
.fb-03__msg:hover .fb-03__ico::after {
animation: fb-03-pulse 2.1s ease-out 3;
}
@keyframes fb-03-pulse {
0% {
transform: scale(.86);
opacity: .8;
}
70% {
transform: scale(1.35);
opacity: 0;
}
100% {
transform: scale(1.35);
opacity: 0;
}
}
.fb-03__status {
position: absolute;
right: 1px;
bottom: 1px;
width: 11px;
height: 11px;
border-radius: 50%;
background: oklch(0.85 0.19 130);
box-shadow: 0 0 0 2.5px var(--fb-03-brandDeep);
}
.fb-03__label {
overflow: hidden;
white-space: nowrap;
display: grid;
gap: 1px;
min-width: 0;
}
.fb-03__msg:is(:hover,:focus-visible) .fb-03__label {
padding-right: 6px;
}
.fb-03__label b {
font-size: 15px;
font-weight: 650;
letter-spacing: -.01em;
}
.fb-03__label i {
font-style: normal;
font-size: 11.5px;
color: oklch(0.97 0.02 152/.82);
}
@media (prefers-reduced-motion: reduce) {
.fb-03__msg {
transition: none;
}
.fb-03__ico::after {
animation: none;
opacity: 0;
}
}
```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: WhatsApp Floating Message Button CSS
Source: https://codefronts.com/components/css-floating-buttons/whatsapp-floating-message-button-css/
The green "message us" button every small-business site puts in the bottom-right corner, done properly: a resting circle that expands into a labelled pill on hover and focus, an attention pulse that runs three times and then stops, and an online/away status dot. The label is real text inside the button, so it is announced, translatable and searchable — not a tooltip bolted on with a data-attribute.
## HTML
```html
<section class="fb-03" aria-label="Floating message button demo">
<div class="fb-03__page">
<div class="fb-03__hero">
<p class="fb-03__eyebrow">grid-template-columns 0fr → 1fr</p>
<h2 class="fb-03__title">A circle that becomes a sentence</h2>
<p class="fb-03__sub">Hover or tab to the button. The label is real text inside the same element, so the accessible name never changes as it expands.</p>
<p class="fb-03__chip">3-cycle pulse · intrinsic width · status dot</p>
</div>
<div class="fb-03__cards" aria-hidden="true">
<div class="fb-03__card"></div><div class="fb-03__card"></div><div class="fb-03__card"></div>
</div>
</div>
<button class="fb-03__msg" type="button">
<span class="fb-03__ico">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5.6A2.4 2.4 0 0 1 6.4 3.2h11.2A2.4 2.4 0 0 1 20 5.6v7.2a2.4 2.4 0 0 1-2.4 2.4h-6.24l-4.02 3.2A.6.6 0 0 1 6.4 17.9v-2.7H6.4A2.4 2.4 0 0 1 4 12.8V5.6z" fill="currentColor"/><circle cx="9" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="12" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/><circle cx="15" cy="9.2" r="1.05" fill="var(--fb-03-brand)"/></svg>
<span class="fb-03__status" aria-hidden="true"></span>
</span>
<span class="fb-03__label"><b>Message us</b><i>Typically replies in 5 min</i></span>
</button>
</section>
```
## CSS
```css
.fb-03 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--fb-03-bg);
--fb-03-bg: oklch(0.97 0.012 150);
--fb-03-brand: oklch(0.62 0.16 152);
--fb-03-brandDeep: oklch(0.5 0.14 152);
--fb-03-ink: oklch(0.24 0.03 160);
--fb-03-mut: oklch(0.54 0.02 160);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
color: var(--fb-03-ink);
position: relative;
display: grid;
place-items: center;
padding: clamp(20px,5vw,56px);
overflow: hidden;
}
.fb-03 *,
.fb-03 *::before,
.fb-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fb-03__page {
width: min(100%,660px);
display: grid;
gap: clamp(20px,4vw,34px);
justify-items: center;
text-align: center;
}
.fb-03__hero {
display: grid;
gap: 11px;
justify-items: center;
}
.fb-03__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--fb-03-brandDeep);
}
.fb-03__title {
font-size: clamp(26px,4.6vw,44px);
line-height: 1.05;
letter-spacing: -.03em;
font-weight: 700;
text-wrap: balance;
}
.fb-03__sub {
max-width: 50ch;
font-size: clamp(13.5px,1.7vw,16.5px);
line-height: 1.62;
color: var(--fb-03-mut);
text-wrap: pretty;
}
.fb-03__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--fb-03-mut);
padding: 8px 14px;
border: 1px solid oklch(0.24 0.03 160/.14);
border-radius: 99px;
}
.fb-03__cards {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 14px;
width: 100%;
}
.fb-03__card {
height: clamp(72px,12vw,104px);
border-radius: 16px;
background: oklch(1 0 0);
border: 1px solid oklch(0.24 0.03 160/.09);
box-shadow: 0 10px 26px -20px oklch(0.24 0.03 160/.6);
}
.fb-03__card:nth-child(2) {
background: linear-gradient(120deg,oklch(0.93 0.04 152),oklch(0.98 0.01 152));
}
.fb-03__msg {
position: absolute;
right: clamp(16px,3vw,30px);
bottom: calc(clamp(16px,3vw,30px) + env(safe-area-inset-bottom));
z-index: 5;
display: grid;
grid-template-columns: auto 0fr;
align-items: center;
gap: 0;
min-height: 60px;
padding: 8px;
border: 0;
border-radius: 99px;
background: linear-gradient(180deg,var(--fb-03-brand),var(--fb-03-brandDeep));
color: oklch(0.99 0.01 152);
cursor: pointer;
text-align: left;
box-shadow: 0 8px 26px -8px oklch(0.5 0.14 152/.7),0 2px 6px oklch(0.3 0.06 152/.25);
transition: grid-template-columns .34s cubic-bezier(.2,.8,.2,1),gap .34s cubic-bezier(.2,.8,.2,1),box-shadow .3s ease,transform .2s ease;
}
.fb-03__msg:is(:hover,:focus-visible) {
grid-template-columns: auto 1fr;
gap: 12px;
box-shadow: 0 14px 34px -8px oklch(0.5 0.14 152/.75);
}
.fb-03__msg:focus-visible {
outline: 3px solid var(--fb-03-brandDeep);
outline-offset: 3px;
}
.fb-03__msg:active {
transform: scale(.97);
}
.fb-03__ico {
position: relative;
flex: none;
width: 44px;
height: 44px;
border-radius: 50%;
display: grid;
place-items: center;
background: oklch(1 0 0/.16);
}
.fb-03__ico svg {
width: 26px;
height: 26px;
}
.fb-03__ico::after {
content: "";
position: absolute;
inset: -9px;
border-radius: 50%;
border: 2px solid oklch(1 0 0/.55);
animation: fb-03-pulse 2.1s ease-out 3;
pointer-events: none;
}
.fb-03__msg:hover .fb-03__ico::after {
animation: fb-03-pulse 2.1s ease-out 3;
}
@keyframes fb-03-pulse {
0% {
transform: scale(.86);
opacity: .8;
}
70% {
transform: scale(1.35);
opacity: 0;
}
100% {
transform: scale(1.35);
opacity: 0;
}
}
.fb-03__status {
position: absolute;
right: 1px;
bottom: 1px;
width: 11px;
height: 11px;
border-radius: 50%;
background: oklch(0.85 0.19 130);
box-shadow: 0 0 0 2.5px var(--fb-03-brandDeep);
}
.fb-03__label {
overflow: hidden;
white-space: nowrap;
display: grid;
gap: 1px;
min-width: 0;
}
.fb-03__msg:is(:hover,:focus-visible) .fb-03__label {
padding-right: 6px;
}
.fb-03__label b {
font-size: 15px;
font-weight: 650;
letter-spacing: -.01em;
}
.fb-03__label i {
font-style: normal;
font-size: 11.5px;
color: oklch(0.97 0.02 152/.82);
}
@media (prefers-reduced-motion: reduce) {
.fb-03__msg {
transition: none;
}
.fb-03__ico::after {
animation: none;
opacity: 0;
}
}
```How this works
The expand is a grid trick, not a width animation. The label lives in a track that animates from 0fr to 1fr:
.msg{ display:grid; grid-template-columns:auto 0fr; gap:0;
transition:grid-template-columns .34s cubic-bezier(.2,.8,.2,1), gap .34s }
.msg:is(:hover,:focus-visible){ grid-template-columns:auto 1fr; gap:12px }
.msg__label{ overflow:hidden; white-space:nowrap }Animating grid-template-columns beats animating width or max-width because the end state is the label's intrinsic width — no magic number, so it works in every language without re-tuning. It beats transform:scaleX because the text is never distorted.
The pulse is a pseudo-element ring with animation-iteration-count:3. Infinite pulses on a persistent widget are an accessibility and attention problem; three cycles announce the button, then get out of the way. It restarts on hover, which is where the user's attention already is.
Because the pill is the same element in both states, focus never moves, the accessible name never changes, and there is no second hidden element to keep in sync.
Make it yours
- Left-side placement: switch
righttoleftand setdirection:rtlon the button (withdirection:ltron the label) so the pill grows away from the edge. - Channel colour:
--fb-03-branddrives circle, ring, and shadow. Blue for Messenger-style, indigo for a generic support channel. - Always-open pill: delete the collapsed grid state and ship
grid-template-columns:auto 1fr— higher click-through, more visual weight. - Stacked channels: repeat the button in a column with
gap:12pxand give each a different brand token for a multi-channel contact stack.
Gotchas — read before shipping
- Never animate
widthto a hard-coded px value for a text pill — translations overflow or get clipped. The 0fr → 1fr grid track measures the text for you. - A permanently pulsing widget competes with your content and can trigger motion sensitivity. Cap iterations and always honour prefers-reduced-motion.
- Fixed-position widgets sit on top of mobile bottom navigation and cookie bars. Reserve space with
bottom:calc(20px + env(safe-area-inset-bottom))and check your consent banner's z-index. - If the label is hidden with
display:noneinstead of an overflow-clipped track, the accessible name disappears in the collapsed state. Clip it, don't remove it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
Animatable grid-template-columns needs Chrome 107+, Firefox 127+ and Safari 16+. In older engines the pill snaps open instantly instead of sliding — same functionality, no animation.