Spinner

.spinner-border draws a spinning ring and .spinner-grow a dot that grows and fades. Both use currentColor, so .text-* utilities recolor them; .spinner-border-sm and .spinner-grow-sm fit inside buttons.

Border and grow spinners, colors, sizes and a loading buttonHTMLLive
<link rel=stylesheet href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css">
<div class="p-3 d-flex align-items-center gap-3">
  <div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div>
  <div class="spinner-border text-success" style="width: 3rem; height: 3rem"></div>
  <div class="spinner-grow text-danger"></div>
  <button class="btn btn-primary" type="button" disabled>
    <span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
    <span role="status">Saving...</span></button>
</div>
<script>// Demo only: freeze the grow spinner at full size for the screenshot.
  const grow = document.querySelector('.spinner-grow').getAnimations()[0];
  grow.pause(); grow.currentTime = 375;</script>
Browser output of Listing 3.42
Browser output of 42

The ring is a currentColor border with a transparent right side, rotated every 0.75 seconds (1.5 seconds when the user prefers reduced motion). Give one element role="status" with hidden text so screen readers announce the wait.