AI CSS Animations

Glass Chat Bubbles

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

  1. Still on for Friday?
  2. Yes! 7pm, the usual place
  3. Perfect, I'll bring cake

HTML

<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>

CSS

.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;
  }
}

Customizing this snippet

  • Color: set --gls-color on the element or a parent. Currently previewing #3b82f6.
  • Base size: set --gls-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 16px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar glass effects