Loading Animation

Motion indicating an operation in progress.

Loading results…

html

<div class="mot-load-stage" data-style="spinner">
  <div class="mot-load-spinner" style="border-top-color:#BFD9F2;"></div>
  <div class="mot-load-dots"><span style="background:#BFD9F2;"></span><span style="background:#BFD9F2;"></span><span style="background:#BFD9F2;"></span></div>
  <div class="mot-load-bar"><span style="background:#BFD9F2;"></span></div>
  <p role="status" aria-live="polite" class="mot-load-caption">Loading results&hellip;</p>
</div>
<style>
.mot-load-stage { width: 100%; max-width: 320px; padding: 20px; background: #FAF7F2; border: 1px solid #24143022; border-radius: 12px; font: 13px system-ui, sans-serif; color: #241430aa; text-align: center; }
.mot-load-spinner, .mot-load-dots, .mot-load-bar { display: none; }
[data-style="spinner"] .mot-load-spinner { display: inline-block; width: 34px; height: 34px; border: 4px solid #24143022; border-radius: 999px; }
[data-style="dots"] .mot-load-dots { display: flex; gap: 8px; justify-content: center; }
[data-style="dots"] .mot-load-dots span { width: 10px; height: 10px; border-radius: 999px; display: block; }
[data-style="bar"] .mot-load-bar { height: 8px; background: #24143014; border-radius: 999px; overflow: hidden; }
[data-style="bar"] .mot-load-bar span { display: block; height: 100%; width: 40%; border-radius: 999px; }
.mot-load-caption { margin: 12px 0 0; }
@media (prefers-reduced-motion: no-preference) {
  [data-style="spinner"] .mot-load-spinner { animation: mot-load-spin 1000ms linear infinite; }
  [data-style="dots"] .mot-load-dots span:nth-child(1) { animation: mot-load-bounce 1000ms ease-in-out infinite; }
  [data-style="dots"] .mot-load-dots span:nth-child(2) { animation: mot-load-bounce 1000ms ease-in-out .15s infinite; }
  [data-style="dots"] .mot-load-dots span:nth-child(3) { animation: mot-load-bounce 1000ms ease-in-out .3s infinite; }
  [data-style="bar"] .mot-load-bar span { animation: mot-load-slide 1000ms ease-in-out infinite; }
  @keyframes mot-load-spin { to { transform: rotate(360deg); } }
  @keyframes mot-load-bounce { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
  @keyframes mot-load-slide { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
}
</style>

tailwind

<div class="w-full max-w-[320px] p-5 bg-[#FAF7F2] border border-[#24143022] rounded-xl font-sans text-sm text-[#241430]/70 text-center" data-style="spinner">
  <div class="tw-load-spinner hidden w-[34px] h-[34px] rounded-full border-4 border-[#24143022] mx-auto" style="border-top-color:#BFD9F2;"></div>
  <div class="tw-load-dots hidden gap-2 justify-center"><span class="w-2.5 h-2.5 rounded-full block" style="background:#BFD9F2;"></span><span class="w-2.5 h-2.5 rounded-full block" style="background:#BFD9F2;"></span><span class="w-2.5 h-2.5 rounded-full block" style="background:#BFD9F2;"></span></div>
  <div class="tw-load-bar hidden h-2 bg-[#24143014] rounded-full overflow-hidden"><span class="block h-full w-2/5 rounded-full" style="background:#BFD9F2;"></span></div>
  <p role="status" aria-live="polite" class="mt-3">Loading results&hellip;</p>
</div>
<style>
[data-style="spinner"] .tw-load-spinner { display: inline-block; }
[data-style="dots"] .tw-load-dots { display: flex; }
[data-style="bar"] .tw-load-bar { display: block; }
@media (prefers-reduced-motion: no-preference) {
  [data-style="spinner"] .tw-load-spinner { animation: mot-load-tw-spin 1000ms linear infinite; }
  [data-style="dots"] .tw-load-dots span:nth-child(1) { animation: mot-load-tw-bounce 1000ms ease-in-out infinite; }
  [data-style="dots"] .tw-load-dots span:nth-child(2) { animation: mot-load-tw-bounce 1000ms ease-in-out .15s infinite; }
  [data-style="dots"] .tw-load-dots span:nth-child(3) { animation: mot-load-tw-bounce 1000ms ease-in-out .3s infinite; }
  [data-style="bar"] .tw-load-bar span { animation: mot-load-tw-slide 1000ms ease-in-out infinite; }
  @keyframes mot-load-tw-spin { to { transform: rotate(360deg); } }
  @keyframes mot-load-tw-bounce { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
  @keyframes mot-load-tw-slide { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
}
</style>

react

export function LoadingAnimation({
  style = "spinner",
  duration = 1000,
  message = "Loading results",
  accent = "#BFD9F2",
}) {
  return (
    <div style={{ width: "100%", maxWidth: 320, padding: 20, background: "#FAF7F2", border: "1px solid #24143022", borderRadius: 12, fontFamily: "system-ui, sans-serif", fontSize: 13, color: "#241430aa", textAlign: "center" }}>
      {style === "spinner" && (
        <div className="spinner" style={{ display: "inline-block", width: 34, height: 34, border: "4px solid #24143022", borderTopColor: accent, borderRadius: 999 }} />
      )}
      {style === "dots" && (
        <div style={{ display: "flex", gap: 8, justifyContent: "center" }}>
          {[0, 1, 2].map((i) => (
            <span key={i} className={`dot dot-${i}`} style={{ width: 10, height: 10, borderRadius: 999, background: accent, display: "block" }} />
          ))}
        </div>
      )}
      {style === "bar" && (
        <div style={{ height: 8, background: "#24143014", borderRadius: 999, overflow: "hidden" }}>
          <span className="bar" style={{ display: "block", height: "100%", width: "40%", borderRadius: 999, background: accent }} />
        </div>
      )}
      <p role="status" aria-live="polite" style={{ margin: "12px 0 0" }}>{message}&hellip;</p>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .spinner { animation: mot-load-spin ${duration}ms linear infinite; }
          .dot-0 { animation: mot-load-bounce ${duration}ms ease-in-out infinite; }
          .dot-1 { animation: mot-load-bounce ${duration}ms ease-in-out .15s infinite; }
          .dot-2 { animation: mot-load-bounce ${duration}ms ease-in-out .3s infinite; }
          .bar { animation: mot-load-slide ${duration}ms ease-in-out infinite; }
          @keyframes mot-load-spin { to { transform: rotate(360deg); } }
          @keyframes mot-load-bounce { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
          @keyframes mot-load-slide { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
        }
      `}</style>
    </div>
  );
}