AI CSS Animations

Agent Presence Avatar Stack

A presence bar of stacked agent avatars, each circled by its own spinning working ring. One finishes: its ring closes green, a check badge pops on, a tooltip names it and the summary rolls over.

Also known as: agent avatar stack, multiplayer presence avatars, agents working indicator.

4 agents working3 working, 1 done feat/checkout-v2

HTML

<div class="aia-agent-presence-stack" role="status" aria-label="Four agents working, docs-sync finished">
  <span class="aia-agent-presence-stack-tip" aria-hidden="true"><span>docs-sync finished</span></span>
  <div class="aia-agent-presence-stack-bar">
    <ul class="aia-agent-presence-stack-faces" aria-hidden="true">
      <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 7 4.5 5L5 17M12 17.5h7"/></svg></li>
      <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.4 3.4 5.3 3.4 8.5s-1 6.1-3.4 8.5c-2.4-2.4-3.4-5.3-3.4-8.5s1-6.1 3.4-8.5z"/></svg></li>
      <li class="aia-agent-presence-stack-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg><i><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5.5 12.5 4 4 9-9.5"/></svg></i></li>
      <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.2"/><circle cx="6" cy="18" r="2.2"/><circle cx="18" cy="7" r="2.2"/><path d="M6 8.2v7.6M18 9.2c0 5-6 4.2-11 7.2"/></svg></li>
      <li class="aia-agent-presence-stack-more">+2</li>
    </ul>
    <span class="aia-agent-presence-stack-text">
      <span class="aia-agent-presence-stack-swap"><b>4 agents working</b><b>3 working, 1 done</b></span>
      <span class="aia-agent-presence-stack-sub">feat/checkout-v2</span>
    </span>
  </div>
</div>

CSS

.aia-agent-presence-stack {
  --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;
  --d: 7s;
  --ok: #4ade80;
  --bg: color-mix(in srgb, var(--c) 7%, #111114);
  position: relative;
  display: inline-block;
  padding-top: 2.4em;
}
.aia-agent-presence-stack-bar {
  display: flex;
  align-items: center;
  gap: 0.85em;
  padding: 0.45em 1.15em 0.45em 0.5em;
  border-radius: 999em;
  background: var(--bg);
  color: #f4f4f5;
  box-shadow: 0 0 0 1px rgb(255 255 255 / 0.09), 0 1em 2.2em -1em rgb(0 0 0 / 0.55);
}
.aia-agent-presence-stack-faces {
  display: flex;
  margin: 0;
  padding: 0.2em;
  list-style: none;
}
.aia-agent-presence-stack-faces li {
  --hue: var(--c);
  --spin: 1.1s;
  position: relative;
  display: grid;
  place-items: center;
  width: 2.3em;
  height: 2.3em;
  margin-left: -0.45em;
  border-radius: 50%;
  background: linear-gradient(140deg, color-mix(in srgb, var(--hue) 85%, #fff), color-mix(in srgb, var(--hue) 70%, #000));
  color: #fff;
  box-shadow: 0 0 0 0.17em var(--bg);
}
.aia-agent-presence-stack-faces li:first-child { margin-left: 0; }
.aia-agent-presence-stack-faces li:nth-child(2) { --hue: color-mix(in srgb, var(--c) 45%, #0ea5e9); --spin: 1.45s; }
.aia-agent-presence-stack-faces li:nth-child(3) { --hue: color-mix(in srgb, var(--c) 45%, #14b8a6); --spin: 0.95s; z-index: 1; }
.aia-agent-presence-stack-faces li:nth-child(4) { --hue: color-mix(in srgb, var(--c) 45%, #f59e0b); --spin: 1.25s; }
.aia-agent-presence-stack-faces svg {
  width: 1.05em;
  height: 1.05em;
}
/* the working ring, sitting in the gap around each face */
.aia-agent-presence-stack-faces li:not(.aia-agent-presence-stack-more)::before {
  content: "";
  position: absolute;
  inset: -0.17em;
  border-radius: 50%;
  border: 0.13em solid rgb(255 255 255 / 0.1);
  border-top-color: color-mix(in srgb, var(--c) 40%, #fff);
  border-right-color: color-mix(in srgb, var(--c) 40%, #fff);
  animation: aia-agent-presence-stack-spin var(--spin) linear infinite;
}
.aia-agent-presence-stack-faces .aia-agent-presence-stack-more {
  background: rgb(255 255 255 / 0.1);
  color: rgb(255 255 255 / 0.75);
  font-size: 0.7em;
  font-weight: 700;
  width: 3.3em;
  height: 3.3em;
  margin-left: -0.64em;
}
.aia-agent-presence-stack-done {
  animation: aia-agent-presence-stack-lift var(--d) cubic-bezier(0.34, 1.4, 0.64, 1) infinite;
}
.aia-agent-presence-stack-faces .aia-agent-presence-stack-done::before {
  animation:
    aia-agent-presence-stack-spin var(--spin) linear infinite,
    aia-agent-presence-stack-out var(--d) ease infinite;
}
.aia-agent-presence-stack-done::after {
  content: "";
  position: absolute;
  inset: -0.17em;
  border-radius: 50%;
  border: 0.13em solid var(--ok);
  animation: aia-agent-presence-stack-ring var(--d) cubic-bezier(0.34, 1.4, 0.64, 1) infinite;
}
.aia-agent-presence-stack-done i {
  position: absolute;
  right: -0.3em;
  bottom: -0.3em;
  display: grid;
  place-items: center;
  width: 1.05em;
  height: 1.05em;
  border-radius: 50%;
  background: var(--ok);
  color: #052e16;
  box-shadow: 0 0 0 0.15em var(--bg);
  animation: aia-agent-presence-stack-badge var(--d) cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.aia-agent-presence-stack-done i svg {
  width: 0.72em;
  height: 0.72em;
  stroke-width: 3.6;
}
.aia-agent-presence-stack-text {
  display: grid;
  gap: 0.1em;
  line-height: 1.25;
}
.aia-agent-presence-stack-swap {
  display: grid;
  font-size: 0.86em;
}
.aia-agent-presence-stack-swap b {
  grid-area: 1 / 1;
  white-space: nowrap;
}
.aia-agent-presence-stack-swap b:first-child { animation: aia-agent-presence-stack-a var(--d) ease infinite; }
.aia-agent-presence-stack-swap b:last-child { animation: aia-agent-presence-stack-b var(--d) ease infinite; }
.aia-agent-presence-stack-sub {
  color: rgb(255 255 255 / 0.5);
  font: 0.68em ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
}
.aia-agent-presence-stack-tip {
  position: absolute;
  top: 0;
  left: 5.15em;
  display: flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.35em 0.65em;
  border-radius: 0.55em;
  background: var(--bg);
  color: #f4f4f5;
  box-shadow: 0 0 0 1px rgb(255 255 255 / 0.1), 0 0.6em 1.2em -0.6em rgb(0 0 0 / 0.5);
  white-space: nowrap;
  animation: aia-agent-presence-stack-tip var(--d) cubic-bezier(0.34, 1.4, 0.64, 1) infinite;
}
.aia-agent-presence-stack-tip::before {
  content: "";
  width: 0.45em;
  height: 0.45em;
  border-radius: 50%;
  background: var(--ok);
}
.aia-agent-presence-stack-tip::after {
  content: "";
  position: absolute;
  left: calc(50% - 0.3em);
  bottom: -0.3em;
  width: 0.6em;
  height: 0.6em;
  background: var(--bg);
  transform: rotate(45deg);
  box-shadow: 1px 1px 0 rgb(255 255 255 / 0.1);
}
.aia-agent-presence-stack-tip span {
  font-size: 0.74em;
  font-weight: 600;
}
@keyframes aia-agent-presence-stack-out {
  0%, 40% { opacity: 1; }
  45%, 95% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes aia-agent-presence-stack-ring {
  0%, 41% { opacity: 0; transform: scale(0.8); }
  48%, 94% { opacity: 1; transform: scale(1); }
  98%, 100% { opacity: 0; transform: scale(1); }
}
@keyframes aia-agent-presence-stack-lift {
  0%, 41%, 100% { transform: none; }
  47% { transform: translateY(-0.35em); }
  54%, 96% { transform: none; }
}
@keyframes aia-agent-presence-stack-badge {
  0%, 45% { opacity: 0; transform: scale(0); }
  52%, 93% { opacity: 1; transform: scale(1); }
  97%, 100% { opacity: 0; transform: scale(0.4); }
}
@keyframes aia-agent-presence-stack-tip {
  0%, 48% { opacity: 0; transform: translate(-50%, 0.5em) scale(0.92); }
  55%, 86% { opacity: 1; transform: translate(-50%, 0); }
  91%, 100% { opacity: 0; transform: translate(-50%, -0.2em); }
}
@keyframes aia-agent-presence-stack-a {
  0%, 45%, 100% { opacity: 1; transform: none; }
  49%, 96% { opacity: 0; transform: translateY(-0.5em); }
}
@keyframes aia-agent-presence-stack-b {
  0%, 45%, 100% { opacity: 0; transform: translateY(0.5em); }
  49%, 96% { opacity: 1; transform: none; }
}
@keyframes aia-agent-presence-stack-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .aia-agent-presence-stack *,
  .aia-agent-presence-stack *::before,
  .aia-agent-presence-stack *::after {
    animation-play-state: paused;
    animation-delay: calc(var(--d) * -0.7);
  }
}

@media (prefers-reduced-motion: reduce) {
  .aia-agent-presence-stack, .aia-agent-presence-stack *, .aia-agent-presence-stack::before, .aia-agent-presence-stack::after, .aia-agent-presence-stack *::before, .aia-agent-presence-stack *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Customizing this snippet

  • Color: set --aia-color on the element or a parent. Currently previewing #3b82f6.
  • Base size: set --aia-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 ai animations