<main>
  <h2>Submit Button with Pure CSS Loading Spinner</h2>

  <p>The submit button below will enable a loading spinner when clicked. The button will be disabled while the loading spinner is present. The JavaScript mimics an asyncronous action using <code>setTimeout()</code> but this would be replaced by something else in your site/app code.</p>

  <form onsubmit="return false;">
    <fieldset>
      <p><label for="un">Username: </label><input id="um"></p>
      <p><label for="pa">Password: </label><input id="pa" type="password"></p>
      <button>SUBMIT FORM<span class="spinner"></span></button>
    </fieldset>
  </form>

</main>
/* This is the submit button styles */
button {
  display: block;
  margin: 0 auto;
  padding: .6em .8em;
   /* Font-size is the root value that determines size of spinner parts. Change this to whatever you want and spinner elements will size to match. */
  font-size: 20px;
  font-weight: bold;
  border-radius: .4em;
  border: none;
  overflow: hidden;
  cursor: pointer;
  position: relative;
  transition: all 1s;
}

/* focus/disabled styles, you can change this for accessibility */
button:focus, button:disabled {
  outline: none;
  background: #aaa;
}

/* This is the space for the spinner to appear, applied to the button */
.spin {
  padding-left: 2.5em;
  display: block;
}

/* position of the spinner when it appears, you might have to change these values */
.spin .spinner {
  left: -.6em;
  top: .4em;
  width: 2.5em;
  display: block;
  position: absolute;
}

/* spinner animation */
@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  
  100% {
    transform: rotate(360deg);
  }
}

/* The actual spinner element is a pseudo-element */
.spin .spinner::before {
  content: "";
  width: 1.5em; /* Size of the spinner */
  height: 1.5em; /* Change as desired */
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 50%;
  border: solid .35em #999; /* Thickness/color of spinner track */
  border-bottom-color: #555; /* Color of variant spinner piece */
  animation: .8s linear infinite spinner; /* speed of spinner */
  transform: translate(-50%, -50%);
  will-change: transform;
}

/* optional, but it will affect the size if changed */
*, *::before, *::after {
  box-sizing: border-box;
}

/* generic document styles below */
body {
  font-family: Arial, sans-serif;
  font-size: 20px;
  padding: 0 20px;
}

main {
  text-align: center;
  margin: 0 auto;
  max-width: 800px;
}

p {
  text-align: left;
  padding: 0 20px;
}

form p {
  text-align: center;  
}

fieldset {
  border-radius: 10px;
}

code {
  color: firebrick;
}
let btn = document.querySelector('button');

btn.addEventListener('click', function () {
  // form submission starts
  // button is disabled
  btn.classList.add('spin');
  btn.disabled = true;
  
  // This disables the whole form via the fieldset
  btn.form.firstElementChild.disabled = true;
  
  // this setTimeout call mimics some asyncronous action
  // you would have something else here
  window.setTimeout(function () {
    // when asyncronous action is done, remove the spinner
    // re-enable button/fieldset
    btn.classList.remove('spin');
    btn.disabled = false;
    btn.form.firstElementChild.disabled = false;
  }, 4000);
}, false);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.