A user message springs up into the thread, the thread scrolls to make room, and the assistant bubble arrives as typing dots that unfurl into the reply. The bubble opens with an animated clip-path.
Also known as: chat message animation css, message bubble animation, ai chat reply animation, chat bubble pop in.
<div class="aia-message-arriving" role="status" aria-label="New reply">
<div class="aia-message-arriving-thread">
<p class="aia-message-arriving-user">Can you make this headline shorter?</p>
<div class="aia-message-arriving-reply">
<span class="aia-message-arriving-dots" aria-hidden="true"><i></i><i></i><i></i></span>
<p class="aia-message-arriving-text">Try “Ship fewer, better things.”</p>
</div>
</div>
</div>.aia-message-arriving {
--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;
width: 22em;
max-width: 100%;
}
.aia-message-arriving-thread {
display: flex;
flex-direction: column;
gap: 0.55em;
animation: aia-message-arriving-scroll 8s infinite;
}
.aia-message-arriving-user {
align-self: flex-end;
margin: 0;
padding: 0.6em 0.95em;
border-radius: 1.15em 1.15em 0.4em 1.15em;
background: var(--c);
color: #fff;
transform-origin: 100% 100%;
animation: aia-message-arriving-user 8s infinite;
}
.aia-message-arriving-reply {
position: relative;
align-self: flex-start;
padding: 0.6em 0.95em;
border-radius: 1.15em 1.15em 1.15em 0.4em;
background: color-mix(in srgb, currentColor 7%, transparent);
transform-origin: 0 100%;
animation: aia-message-arriving-reply 8s infinite;
}
.aia-message-arriving-text {
margin: 0;
white-space: nowrap;
animation: aia-message-arriving-text 8s infinite;
}
.aia-message-arriving-dots {
position: absolute;
left: 0.95em;
top: 50%;
display: flex;
gap: 0.28em;
translate: 0 -50%;
animation: aia-message-arriving-dots 8s infinite;
}
.aia-message-arriving-dots i {
width: 0.45em;
height: 0.45em;
border-radius: 50%;
background: currentColor;
animation: aia-message-arriving-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
.aia-message-arriving-dots i:nth-child(2) { animation-delay: 0.14s; }
.aia-message-arriving-dots i:nth-child(3) { animation-delay: 0.28s; }
@keyframes aia-message-arriving-scroll {
0%, 15% { transform: translateY(1.5em); animation-timing-function: cubic-bezier(0.3, 1.3, 0.5, 1); }
24%, 100% { transform: none; }
}
@keyframes aia-message-arriving-user {
0%, 3% { opacity: 0; transform: translateY(0.8em) scale(0.92); animation-timing-function: cubic-bezier(0.3, 1.45, 0.5, 1); }
11%, 90% { opacity: 1; transform: none; }
97%, 100% { opacity: 0; transform: none; }
}
@keyframes aia-message-arriving-reply {
0%, 15% {
opacity: 0;
transform: scale(0.85);
clip-path: inset(0 calc(100% - 3.6em) 0 0 round 1.15em 1.15em 1.15em 0.4em);
animation-timing-function: cubic-bezier(0.3, 1.45, 0.5, 1);
}
21%, 42% {
opacity: 1;
transform: none;
clip-path: inset(0 calc(100% - 3.6em) 0 0 round 1.15em 1.15em 1.15em 0.4em);
animation-timing-function: cubic-bezier(0.25, 1, 0.35, 1);
}
52%, 90% { opacity: 1; transform: none; clip-path: inset(0 0 0 0 round 1.15em 1.15em 1.15em 0.4em); }
97%, 100% { opacity: 0; transform: none; clip-path: inset(0 0 0 0 round 1.15em 1.15em 1.15em 0.4em); }
}
@keyframes aia-message-arriving-dots {
0%, 42% { opacity: 1; }
46%, 100% { opacity: 0; }
}
@keyframes aia-message-arriving-text {
0%, 44% { opacity: 0; filter: blur(0.2em); }
54%, 100% { opacity: 1; filter: blur(0); }
}
@keyframes aia-message-arriving-dot {
0%, 60%, 100% { transform: translateY(0); opacity: 0.35; }
30% { transform: translateY(-0.25em); opacity: 0.85; }
}
@media (prefers-reduced-motion: reduce) {
.aia-message-arriving-thread,
.aia-message-arriving-user,
.aia-message-arriving-reply,
.aia-message-arriving-text,
.aia-message-arriving-dots,
.aia-message-arriving-dots i { animation: none; }
.aia-message-arriving-dots { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.aia-message-arriving, .aia-message-arriving *, .aia-message-arriving::before, .aia-message-arriving::after, .aia-message-arriving *::before, .aia-message-arriving *::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.