Frosted glass
A translucent pane over slowly moving color.
A short chat plays out over a drifting wallpaper: frosted incoming bubbles, tinted outgoing ones, a typing indicator and a glass compose bar. Each bubble pops from its tail with a small overshoot.
Also known as: glassmorphism chat ui css, imessage chat bubbles css, frosted message bubble.
Today 18:24
<div class="gls-glass-chat-bubbles">
<span class="gls-glass-chat-bubbles-wall" aria-hidden="true"></span>
<div class="gls-glass-chat-bubbles-chat">
<p class="gls-glass-chat-bubbles-time">Today 18:24</p>
<ol class="gls-glass-chat-bubbles-thread" aria-label="Messages">
<li class="gls-glass-chat-bubbles-in gls-glass-chat-bubbles-m1">Still on for Friday?</li>
<li class="gls-glass-chat-bubbles-out gls-glass-chat-bubbles-m2">Yes! 7pm, the usual place</li>
<li class="gls-glass-chat-bubbles-in gls-glass-chat-bubbles-typing" aria-hidden="true"><i></i><i></i><i></i></li>
<li class="gls-glass-chat-bubbles-in gls-glass-chat-bubbles-m3">Perfect, I'll bring cake</li>
</ol>
<div class="gls-glass-chat-bubbles-compose" aria-hidden="true">
<span class="gls-glass-chat-bubbles-field">Message</span>
<span class="gls-glass-chat-bubbles-send"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M6 11l6-6 6 6"/></svg></span>
</div>
</div>
</div>.gls-glass-chat-bubbles {
--c: var(--gls-color, #3b82f6);
font-size: var(--gls-size, 16px);
position: relative;
display: grid;
place-items: center;
width: 22em;
height: 12em;
border-radius: 1.25em;
overflow: hidden;
isolation: isolate;
background: color-mix(in srgb, var(--c) 40%, #1e1b4b);
color: #fff;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.3;
}
.gls-glass-chat-bubbles-wall {
position: absolute;
inset: -30%;
z-index: -1;
background:
radial-gradient(38% 50% at 28% 32%, color-mix(in srgb, var(--c) 80%, #fff), transparent 70%),
radial-gradient(42% 48% at 74% 68%, #f472b6, transparent 70%),
radial-gradient(30% 36% at 72% 26%, #fbbf24, transparent 70%),
linear-gradient(160deg, color-mix(in srgb, var(--c) 55%, #1e1b4b), #4c1d95);
animation: gls-glass-chat-bubbles-wall 12s ease-in-out infinite alternate;
}
.gls-glass-chat-bubbles-chat {
display: grid;
gap: 0.5em;
width: 15em;
}
.gls-glass-chat-bubbles-time {
margin: 0;
font-size: 0.55em;
font-weight: 600;
text-align: center;
opacity: 0.8;
}
.gls-glass-chat-bubbles-thread {
display: grid;
gap: 0.35em;
margin: 0;
padding: 0;
list-style: none;
}
.gls-glass-chat-bubbles-in,
.gls-glass-chat-bubbles-out {
grid-column: 1;
max-width: 75%;
padding: 0.5em 0.85em;
border-radius: 1.1em;
font-size: 0.72em;
line-height: 1.3;
animation: 8s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.gls-glass-chat-bubbles-in {
justify-self: start;
border-bottom-left-radius: 0.3em;
background: color-mix(in srgb, #fff 22%, transparent);
-webkit-backdrop-filter: blur(0.9em) saturate(1.5);
backdrop-filter: blur(0.9em) saturate(1.5);
border: 1px solid color-mix(in srgb, #fff 45%, transparent);
box-shadow: 0 0.5em 1.5em -0.5em rgb(0 0 0 / 0.35), inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
transform-origin: 0 100%;
}
.gls-glass-chat-bubbles-out {
justify-self: end;
border: 1px solid rgb(255 255 255 / 0.3);
border-bottom-right-radius: 0.3em;
background: linear-gradient(180deg, rgb(255 255 255 / 0.2), transparent 70%), color-mix(in srgb, var(--c) 75%, transparent);
-webkit-backdrop-filter: blur(0.9em) saturate(1.5);
backdrop-filter: blur(0.9em) saturate(1.5);
box-shadow: 0 0.5em 1.2em -0.6em rgb(0 0 0 / 0.45);
transform-origin: 100% 100%;
}
.gls-glass-chat-bubbles-m1 { grid-row: 1; animation-name: gls-glass-chat-bubbles-m1; }
.gls-glass-chat-bubbles-m2 { grid-row: 2; animation-name: gls-glass-chat-bubbles-m2; }
.gls-glass-chat-bubbles-m3 { grid-row: 3; animation-name: gls-glass-chat-bubbles-m3; }
.gls-glass-chat-bubbles-typing {
grid-row: 3;
display: flex;
gap: 0.28em;
padding: 0.75em 0.85em;
animation-name: gls-glass-chat-bubbles-typing;
}
.gls-glass-chat-bubbles-typing i {
width: 0.5em;
height: 0.5em;
border-radius: 50%;
background: currentColor;
animation: gls-glass-chat-bubbles-dot 1.2s ease-in-out infinite;
}
.gls-glass-chat-bubbles-typing i:nth-child(2) { animation-delay: 0.15s; }
.gls-glass-chat-bubbles-typing i:nth-child(3) { animation-delay: 0.3s; }
.gls-glass-chat-bubbles-compose {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0.15em;
padding: 0.28em 0.28em 0.28em 0.85em;
border-radius: 99em;
background: color-mix(in srgb, #fff 14%, transparent);
-webkit-backdrop-filter: blur(0.9em) saturate(1.5);
backdrop-filter: blur(0.9em) saturate(1.5);
border: 1px solid color-mix(in srgb, #fff 45%, transparent);
box-shadow: 0 0.5em 1.5em -0.5em rgb(0 0 0 / 0.35), inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
}
.gls-glass-chat-bubbles-field { font-size: 0.68em; opacity: 0.75; }
.gls-glass-chat-bubbles-send {
display: grid;
place-items: center;
width: 1.45em;
height: 1.45em;
border-radius: 50%;
background: linear-gradient(180deg, color-mix(in srgb, var(--c) 45%, #fff), var(--c));
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.5);
}
.gls-glass-chat-bubbles-send svg { width: 0.85em; height: 0.85em; }
@keyframes gls-glass-chat-bubbles-m1 {
0% { opacity: 0; transform: translateY(0.6em) scale(0.6); }
5% { opacity: 1; transform: translateY(-0.08em) scale(1.03); }
8%, 88% { opacity: 1; transform: none; }
95%, 100% { opacity: 0; transform: translateY(-0.5em); }
}
@keyframes gls-glass-chat-bubbles-m2 {
0%, 11% { opacity: 0; transform: translateY(0.6em) scale(0.6); }
16% { opacity: 1; transform: translateY(-0.08em) scale(1.03); }
19%, 88% { opacity: 1; transform: none; }
95%, 100% { opacity: 0; transform: translateY(-0.5em); }
}
@keyframes gls-glass-chat-bubbles-typing {
0%, 20% { opacity: 0; transform: scale(0.5); }
24% { opacity: 1; transform: scale(1.05); }
27%, 44% { opacity: 1; transform: none; }
48%, 100% { opacity: 0; transform: scale(0.6); }
}
@keyframes gls-glass-chat-bubbles-m3 {
0%, 46% { opacity: 0; transform: translateY(0.3em) scale(0.7); }
51% { opacity: 1; transform: translateY(-0.08em) scale(1.03); }
54%, 88% { opacity: 1; transform: none; }
95%, 100% { opacity: 0; transform: translateY(-0.5em); }
}
@keyframes gls-glass-chat-bubbles-dot {
0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
30% { transform: translateY(-0.3em); opacity: 1; }
}
@keyframes gls-glass-chat-bubbles-wall {
to { transform: translate(6%, -5%) rotate(8deg) scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
.gls-glass-chat-bubbles, .gls-glass-chat-bubbles *, .gls-glass-chat-bubbles::before, .gls-glass-chat-bubbles::after, .gls-glass-chat-bubbles *::before, .gls-glass-chat-bubbles *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--gls-color on the element or a parent.
Currently previewing #3b82f6.--gls-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.