The round chat button from the corner of a support page: a ring pings out behind it and a greeting bubble pops in, waits, then tucks away again. One button, so the whole thing is one click target.
Also known as: floating chat button css, live chat widget button, whatsapp style chat button.
<button class="btn-chat-fab" type="button" aria-label="Open chat">
<span class="btn-chat-fab-bubble" aria-hidden="true">Hi! Need a hand?</span>
<span class="btn-chat-fab-circle" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3.5c-4.9 0-8.8 3.4-8.8 7.7 0 2.2 1 4.1 2.7 5.5L5 20.5l4.2-2.1c.9.2 1.8.3 2.8.3 4.9 0 8.8-3.4 8.8-7.6S16.9 3.5 12 3.5z"/></svg>
</span>
</button>.btn-chat-fab {
--c: var(--btn-color, #3b82f6);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
padding: 0.75em 1.6em;
border: 0;
border-radius: 0.6em;
font: 600 var(--btn-size, 18px)/1.1 system-ui, sans-serif;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
gap: 0.7em;
padding: 0;
border-radius: 2em;
background: transparent;
color: inherit;
font-weight: 500;}
.btn-chat-fab-circle {
position: relative;
isolation: isolate;
display: grid;
place-items: center;
width: 3.4em;
height: 3.4em;
border-radius: 50%;
background: var(--c);
color: #fff;
box-shadow: 0 0.4em 1em -0.3em color-mix(in srgb, var(--c) 60%, transparent);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.btn-chat-fab-circle::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: 50%;
background: var(--c);
animation: btn-chat-fab-ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.btn-chat-fab-circle svg {
width: 1.55em;
height: 1.55em;
}
.btn-chat-fab-bubble {
padding: 0.6em 0.9em;
border: 1px solid color-mix(in srgb, var(--c) 20%, transparent);
border-radius: 1em 1em 0.25em 1em;
background: color-mix(in srgb, var(--c) 6%, #fff);
color: color-mix(in srgb, var(--c) 25%, #111827);
box-shadow: 0 0.35em 1em -0.3em rgba(15, 23, 42, 0.3);
font-size: 0.85em;
white-space: nowrap;
transform-origin: 100% 100%;
animation: btn-chat-fab-greet 6s infinite;
}
.btn-chat-fab:is(:hover, :focus-visible) .btn-chat-fab-circle { transform: scale(1.08); }
.btn-chat-fab:active .btn-chat-fab-circle { transform: scale(0.94); }
@keyframes btn-chat-fab-ping {
0% { transform: scale(1); opacity: 0.55; }
80%, 100% { transform: scale(1.7); opacity: 0; }
}
@keyframes btn-chat-fab-greet {
0%, 5% { opacity: 0; transform: translateX(0.8em) scale(0.6); animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }
14%, 72% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0); }
80%, 100% { opacity: 0; transform: translateX(0.8em) scale(0.6); }
}
@media (prefers-reduced-motion: reduce) {
.btn-chat-fab, .btn-chat-fab *, .btn-chat-fab::before, .btn-chat-fab::after, .btn-chat-fab *::before, .btn-chat-fab *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--btn-color on the element or a parent.
Currently previewing #3b82f6.--btn-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 18px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.