<a href="https://youtu.be/0Wx9QbRQYuI" target="_blank" data-keyframers-credit style="color: #000"></a>
<script src="https://codepen.io/shshaw/pen/QmZYMG.js"></script>

<form id="app" data-state="off">
  <div class="toggle-wrapper">
    <input id="toggle" name="toggle" type="checkbox">
    <label for="toggle" class="fancy-toggle">
      <div class="bg"></div>
      <div class="circle">
        <svg viewBox="0 0 100 100">
          <circle class="loader" cx="50" cy="50" r="45" pathLength="1" />
        </svg>
      </div>
    </label>
    <output class="status">
      <div class="status-text" data-for="off">Devices Off</div>
      <div class="status-text" data-for="pending">Please Wait...</div>
      <div class="status-text" data-for="on">Devices On</div>
    </output>
  </div>
  
</form>
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');

*, *:before, *:after {
  box-sizing: border-box;
  position: relative;
}

:root {
  --easing: cubic-bezier(.5, 0, .5, 1);
  --duration: .3s;
  --blue: #6C80F5;
}

html, body {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  font-family: 'Open Sans', sans-serif;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* ---------------------------------- */

.toggle-wrapper {
  font-size: 2rem;
  display: flex;
  align-items: center;
}


#toggle {
  width: 2em;
  height: 2em;
  visibility: hidden;
  position: absolute;
}


#app {
  display: flex;
  flex-direction: row;
  color: #404843;
}

.fancy-toggle {
  font-size: 3rem;
  width: 1em;
  height: 1em;
  // border: solid 5px blue;
  padding: 0.2em;
  box-sizing: content-box;
  margin-right: 1.5em;
  transition: transform 0.4s var(--easing);
  transition-property: transform, width, background-color;
  
  .bg { 
    background: grey;
    position: absolute;
    top: 0;
    left: 0;
    width: 175%;
    height: 100%;
    border-radius: 2em;
    box-shadow: 0 .25em .5em rgba(0,0,0,0.1);
    transition: inherit;
  } 
  
  .circle {
    width: 1em;
    height: 1em;
    background: white;
    box-shadow: 0 .25em .5em rgba(0,0,0,0.1);
    border-radius: 50%;
    transition: inherit;
    
    > svg {
      transform: rotate(-90deg); 
      margin: -.1em;
    }
  }
  
  .loader {
    stroke: var(--blue);
    stroke-width: 6;
    fill: none;
    stroke-dasharray: 1 1;
    stroke-dashoffset: -1;
    transition:
      stroke-dasharray calc(var(--timeout, 0) * 0.5ms) linear,
      stroke-dashoffset 1000ms linear;
  }
}

.status {
  overflow: hidden;
  display: grid;
  user-select: none;
  
  > div {
    grid-area: 1 / 1;
  }
}

.status-text {
  transition: transform var(--duration) var(--easing);
  
  &[data-for="off"],
  &[data-for="pending"] {
    transform: translateY(-100%);
  }
  &[data-for="on"] {
    transform: translateY(100%);
  }
  &[data-active] ~ [data-for="pending"] {
    transform: translateY(100%);
  }
  &[data-active] {
    transform: translateY(0); 
  }
}

[data-state="off"] {
  .fancy-toggle .bg {
    background: #E44B45; 
  }
}

[data-state="pending"] {
  .fancy-toggle {
    transform: translateX(50%);
    .bg {
      width: 100%;
      background: #b5b5b5;
    }
    
    .loader {
      stroke-dashoffset: 0; 
    }
  }
}

[data-state="on"] {
  .fancy-toggle {
    .bg {
      background: #1BCE6F;
    }
    
    .circle {
      transform: translateX(100%);
    }
    .loader {
      stroke-dashoffset: 1;
    }
  }
}

body {
  transition: background-color .3s var(--easing);
  background-color: var(--bg-color);
  
  &[data-toggle-state="off"] {
    --bg-color: #FFE3E2;
  }
  
  &[data-toggle-state="pending"] {
    --bg-color: #DCE1FF;
  }
  
  &[data-toggle-state="on"] {
    --bg-color: #E1FCED;
  }
}
View Compiled
console.clear();
const elToggle = document.querySelector('#toggle');
const elApp = document.querySelector('#app');
const elBody = document.body;

// const machine = {
//   initial: 'off',
//   states: {
//     off: {},
//     pending: {},
//     on: {}
//   }
// }

const TIMEOUT = 1000;

elBody.style.setProperty('--timeout', TIMEOUT);

const transition = (state, event) => {
  switch (state) {
    case 'off':
      return 'pending';
    case 'pending':
      switch (event.type) {
        case 'ALL_ON':
          return 'on';
        case 'ALL_OFF':
          return 'off';
        default:
          return state;
      }
    case 'on':
      return 'pending';
    default:
      return 'off';
  }
}

let current = elApp.dataset.state;

function activate(state) {
  elApp.dataset.state = current;
  elBody.dataset.toggleState = current;
  
  document.querySelectorAll(`[data-active]`)
    .forEach(el => delete el.dataset.active);
  document.querySelectorAll(`[data-for="${current}"]`)
    .forEach(el => el.dataset.active = true);
}

activate(current);

function send(event) {
  const prevState = current;
  elApp.dataset.prevState = prevState;
  
  current = transition(current, event);
  
  if (prevState === current) {
    return;
  }
  
  activate(current);
  
  switch (current) {
    case 'off':
      elToggle.indeterminate = false;
      elToggle.checked = false;
      elToggle.readOnly = false;
      break;
    case 'pending':
      elToggle.readOnly = true;
      elToggle.indeterminate = true;
      
      setTimeout(() => {
        if (prevState === 'on') {
          send({ type: 'ALL_OFF' })
        } else {
          send({ type: 'ALL_ON' })
        }
      }, TIMEOUT);
      break;
    case 'on':
      elToggle.readOnly = false;
      elToggle.checked = true;
      elToggle.indeterminate = false;
      break;
    default:
      break;
  }
}

elToggle.addEventListener('click', e => {
  e.preventDefault();
  send({ type: 'TOGGLE' });
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.