AI CSS Animations

Autosave Indicator Spinner

An inline 'Saving' status whose spinner turns into a check as the text rolls over to 'Saved', then back again. Built for editors and settings pages that save as you type.

Also known as: saving indicator, saving to saved animation, autosave status.

HTML

<div class="ldr-autosave-indicator" role="status" aria-label="Saving">
  <span class="ldr-autosave-indicator-icon" aria-hidden="true"><i></i></span>
  <span class="ldr-autosave-indicator-text" aria-hidden="true"><span>Saving…</span><span>Saved</span></span>
</div>

CSS

.ldr-autosave-indicator {
  --s: var(--ldr-size, 48px);
  --c: var(--ldr-color, #3b82f6);
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font: 500 calc(var(--s) / 2.8) / 1.3 system-ui, sans-serif;
  color: color-mix(in srgb, currentColor 70%, transparent);
}
.ldr-autosave-indicator-icon {
  position: relative;
  width: 1.1em;
  height: 1.1em;
}
.ldr-autosave-indicator-icon::before {
  content: "";
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  border: 0.14em solid color-mix(in srgb, var(--ldr-color, #3b82f6) 25%, transparent);
  border-top-color: var(--c);
  border-radius: 50%;
  animation:
    ldr-autosave-indicator-spin 0.7s linear infinite,
    ldr-autosave-indicator-ring 3.2s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.ldr-autosave-indicator-icon i {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--c);
  scale: 0;
  animation: ldr-autosave-indicator-check 3.2s infinite;
}
.ldr-autosave-indicator-icon i::after {
  content: "";
  position: absolute;
  left: 38%;
  top: 22%;
  width: 22%;
  height: 42%;
  border: solid #fff;
  border-width: 0 0.12em 0.12em 0;
  transform: rotate(45deg);
}
.ldr-autosave-indicator-text {
  display: grid;
  height: 1.3em;
  overflow: hidden;
}
.ldr-autosave-indicator-text span {
  grid-area: 1 / 1;
  animation: ldr-autosave-indicator-saving 3.2s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.ldr-autosave-indicator-text span + span {
  color: var(--c);
  animation-name: ldr-autosave-indicator-saved;
}
@keyframes ldr-autosave-indicator-spin {
  to { transform: rotate(360deg); }
}
@keyframes ldr-autosave-indicator-ring {
  0%, 52%   { opacity: 1; scale: 1; }
  60%, 92%  { opacity: 0; scale: 0.5; }
  100%      { opacity: 1; scale: 1; }
}
@keyframes ldr-autosave-indicator-check {
  0%, 54%  { scale: 0; animation-timing-function: cubic-bezier(0.34, 1.7, 0.64, 1); }
  64%, 88% { scale: 1; animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  95%, 100% { scale: 0; }
}
@keyframes ldr-autosave-indicator-saving {
  0%, 52%   { transform: translateY(0); opacity: 1; }
  60%       { transform: translateY(-100%); opacity: 0; }
  61%, 90%  { transform: translateY(100%); opacity: 0; }
  98%, 100% { transform: translateY(0); opacity: 1; }
}
@keyframes ldr-autosave-indicator-saved {
  0%, 54%   { transform: translateY(100%); opacity: 0; }
  62%, 88%  { transform: translateY(0); opacity: 1; }
  96%, 100% { transform: translateY(-100%); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ldr-autosave-indicator, .ldr-autosave-indicator *, .ldr-autosave-indicator::before, .ldr-autosave-indicator::after, .ldr-autosave-indicator *::before, .ldr-autosave-indicator *::after {
    animation-duration: 3s;
  }
}

Customizing this loader

  • Color: set --ldr-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --ldr-size. Every dimension in the snippet is derived from it. Currently previewing 64px.
  • Speed: change the duration on the animation shorthand. Delays on the children are relative, so they keep their rhythm.
  • Accessibility: the root carries role="status" and an aria-label so screen readers announce the wait. The reduced-motion rule at the end slows the animation for users who asked for less motion.

Similar loaders