JSFiddle - React, Tailwind, and code Playground

by JMPerez

HTML

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<form role="form">
  <div class="form-group">
    <input type="text" class="form-control" placeholder="Type something" />
  </div>
<button type="button" class="btn btn-primary default">&nbsp;
    <div class="default">
        Save
    </div>
    <div class="loading">
        <div class="throbber"></div>
        Saving
    </div>
    <div class="end">
        ✔ Saved
    </div>
</button>
</form>

CSS

body {
    padding: 10px;
}

.btn {
    width: 200px;
    position: relative;
}

.btn .loading {
    display: none;
}

.btn.loading .loading {
    display: inline-block;
}

.btn .end{
    display:none;
}

.btn.end .end {
    display:inline-block;
}

.btn .default{
    display:none;
}

.btn.default .default {
    display:inline-block;
}


@-webkit-keyframes throbber {
  0% {
    background: #dde2e7;
  }

  10% {
    background: #6b9dc8;
  }

  40% {
    background: #dde2e7;
  }
}

@-moz-keyframes throbber {
  0% {
    background: #dde2e7;
  }

  10% {
    background: #6b9dc8;
  }

  40% {
    background: #dde2e7;
  }
}

@-o-keyframes throbber {
  0% {
    background: #dde2e7;
  }

  10% {
    background: #6b9dc8;
  }

  40% {
    background: #dde2e7;
  }
}

@keyframes throbber {
  0% {
    background: #dde2e7;
  }

  10% {
    background: #6b9dc8;
  }

  40% {
    background: #dde2e7;
  }
}

/* :not(:required) hides these rules from IE9 and below */
.throbber:not(:required) {
  -webkit-animation: throbber 1000ms 300ms infinite ease-out;
  -moz-animation: throbber 1000ms 300ms infinite ease-out;
  -ms-animation: throbber 1000ms 300ms infinite ease-out;
  -o-animation: throbber 1000ms 300ms infinite ease-out;
  animation: throbber 1000ms 300ms infinite ease-out;
  background: #dde2e7;
  display: inline-block;
  position: relative;
  text-indent: -9999px;
  width: 0.2em;
  height: 0.8em;
  margin: 0 0.8em;
}
.throbber:not(:required):before, .throbber:not(:required):after {
  background: #dde2e7;
  content: '\x200B';
  display: inline-block;
  width: 0.2em;
  height: 0.8em;
  position: absolute;
  top: 0;
}
.throbber:not(:required):before {
  -webkit-animation: throbber 1000ms 150ms infinite ease-out;
  -moz-animation: throbber 1000ms 150ms infinite ease-out;
  -ms-animation: throbber 1000ms 150ms infinite ease-out;
  -o-animation: throbber 1000ms 150ms infinite ease-out;
  animation: throbber 1000ms 150ms infinite ease-out;
  left:...

JavaScript

var button = document.querySelector('.btn');
button.addEventListener('click', function(e) {
    this.classList.remove('default');
    this.classList.add('loading');
    
    setTimeout((function() {
      this.classList.remove('loading');
      this.classList.add('end');
      this.disabled = 'disabled';
    }).bind(this), 2500);
    
});

var input = document.querySelector('input');
input.addEventListener('keyup', function() {
    button.disabled = '';
    button.classList.remove('loading', 'end')
    button.classList.add('default');
});

button.disabled = 'disabled';