A mic button that gets pressed in, fills with the accent and sends rings outward while it is held, then springs back on release. The hint below swaps between Hold to talk and Release to send.
Also known as: push to talk button css, hold to talk animation, mic button animation, voice record button css.
<div class="aia-push-to-talk" role="status" aria-label="Push to talk">
<button class="aia-push-to-talk-btn" type="button" aria-label="Hold to talk">
<span class="aia-push-to-talk-ring"></span>
<span class="aia-push-to-talk-ring aia-push-to-talk-ring-late"></span>
<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21"/></svg>
</button>
<div class="aia-push-to-talk-hint">
<span>Hold to talk</span>
<span class="aia-push-to-talk-live">Release to send</span>
</div>
</div>.aia-push-to-talk {
--c: var(--aia-color, #7c5cff);
font-size: var(--aia-size, 16px);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.4;
--t: 4.4s;
display: grid;
justify-items: center;
gap: 1.2em;
}
.aia-push-to-talk-btn {
position: relative;
display: grid;
place-items: center;
width: 4.6em;
height: 4.6em;
padding: 0;
border: 0;
border-radius: 50%;
font: inherit;
color: inherit;
background: color-mix(in srgb, currentColor 8%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent);
cursor: pointer;
animation: aia-push-to-talk-press var(--t) cubic-bezier(0.3, 0.7, 0.3, 1) infinite;
}
.aia-push-to-talk-btn:focus-visible { outline: 2px solid var(--c); outline-offset: 0.25em; }
.aia-push-to-talk-btn svg {
width: 1.7em;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
}
.aia-push-to-talk-ring {
position: absolute;
inset: 0;
border-radius: 50%;
border: 0.12em solid var(--c);
opacity: 0;
animation: aia-push-to-talk-ring var(--t) cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
}
.aia-push-to-talk-ring-late { animation-delay: calc(var(--t) * -0.75); }
.aia-push-to-talk-hint {
display: grid;
font-size: 0.9em;
font-weight: 500;
}
.aia-push-to-talk-hint > span {
grid-area: 1 / 1;
text-align: center;
color: color-mix(in srgb, currentColor 65%, transparent);
animation: aia-push-to-talk-idle var(--t) ease infinite;
}
.aia-push-to-talk-hint > .aia-push-to-talk-live {
color: var(--c);
opacity: 0;
animation-name: aia-push-to-talk-live;
}
@keyframes aia-push-to-talk-press {
0%, 14% { scale: 1; color: inherit; background: color-mix(in srgb, currentColor 8%, transparent); }
19% { scale: 0.88; color: #fff; background: var(--c); }
25%, 70% { scale: 0.94; color: #fff; background: var(--c); }
77% { scale: 1.06; color: inherit; background: color-mix(in srgb, currentColor 8%, transparent); }
84%, 100% { scale: 1; }
}
@keyframes aia-push-to-talk-ring {
0%, 20% { opacity: 0; scale: 1; }
22% { opacity: 0.7; scale: 1; }
46%, 100% { opacity: 0; scale: 1.9; }
}
@keyframes aia-push-to-talk-idle {
0%, 15%, 76%, 100% { opacity: 1; filter: blur(0); }
20%, 70% { opacity: 0; filter: blur(0.15em); }
}
@keyframes aia-push-to-talk-live {
0%, 16%, 74%, 100% { opacity: 0; filter: blur(0.15em); }
21%, 70% { opacity: 1; filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) {
.aia-push-to-talk-btn,
.aia-push-to-talk-ring,
.aia-push-to-talk-hint > span { animation: none !important; }
}
@media (prefers-reduced-motion: reduce) {
.aia-push-to-talk, .aia-push-to-talk *, .aia-push-to-talk::before, .aia-push-to-talk::after, .aia-push-to-talk *::before, .aia-push-to-talk *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--aia-color on the element or a parent.
Currently previewing #3b82f6.--aia-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.