<div class="card">
  <div class="content-wrapper">
    <h2 class="heading">Accessible Toggle Switch</h2>
    <p>The following demo shows how to build and style a custom toggle switch using a semantic checkbox.</p>
  </div>

  <div class="demo">
    <!-- begin toggle markup   -->

    <label class="toggle" for="uniqueID">
      <input type="checkbox" class="toggle__input" id="uniqueID" />
      <span class="toggle-track">
        <span class="toggle-indicator">
          <!--  This check mark is optional  -->
          <span class="checkMark">
            <svg viewBox="0 0 24 24" id="ghq-svg-check" role="presentation" aria-hidden="true">
              <path d="M9.86 18a1 1 0 01-.73-.32l-4.86-5.17a1.001 1.001 0 011.46-1.37l4.12 4.39 8.41-9.2a1 1 0 111.48 1.34l-9.14 10a1 1 0 01-.73.33h-.01z"></path>
            </svg>
          </span>
        </span>
      </span>
      Enabled toggle label
    </label>

    <!-- end toggle markup   -->

    <label class="toggle" for="disabledDemo">
      <input type="checkbox" class="toggle__input" id="disabledDemo" disabled />
      <span class="toggle-track">
        <span class="toggle-indicator">
          <span class="checkMark">
            <svg viewBox="0 0 24 24" id="ghq-svg-check" role="presentation" aria-hidden="true">
              <path d="M9.86 18a1 1 0 01-.73-.32l-4.86-5.17a1.001 1.001 0 011.46-1.37l4.12 4.39 8.41-9.2a1 1 0 111.48 1.34l-9.14 10a1 1 0 01-.73.33h-.01z"></path>
            </svg>
          </span>
        </span>
      </span>
      Disabled toggle label
    </label>
  </div>
</div>
$toggle-indicator-size: 24px; // changing this number will resize the whole toggle
$track-height: $toggle-indicator-size + 6;
$track-width: $toggle-indicator-size * 2.5;
$highContrastModeSupport: solid 2px transparent;
/* 

The following vars come from my theme. 
You'll need to replace with your own color values. 

- "$light"
- "$mid"
- "$dark"

*/
$track-border: $mid;
$track-background: $light;
$focus-ring: 0px 0px 0px 2px $dark;

// Toggle specific styles
.toggle {
  align-items: center;
  border-radius: 100px;
  display: flex;
  font-weight: 700;
  margin-bottom: 16px;

  &:last-of-type {
    margin: 0;
  }
}

// Since we can't style the checkbox directly, we "hide" it so we can draw the toggle.
.toggle__input {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;

  // This style sets the focus ring. The ":not([disabled])" prevents the focus ring from creating a flash when the element is clicked.
  &:not([disabled]):active + .toggle-track,
  &:not([disabled]):focus + .toggle-track {
    border: 1px solid transparent;
    box-shadow: $focus-ring;
  }

  &:disabled + .toggle-track {
    cursor: not-allowed;
    opacity: 0.7;
  }
}

.toggle-track {
  background: $track-background;
  border: 1px solid $track-border;
  border-radius: 100px;
  cursor: pointer;
  display: flex;
  height: $track-height;
  margin-right: 12px;
  position: relative;
  width: $track-width;
}

.toggle-indicator {
  align-items: center;
  background: $dark;
  border-radius: $toggle-indicator-size;
  bottom: 2px;
  display: flex;
  height: $toggle-indicator-size;
  justify-content: center;
  left: 2px;
  outline: $highContrastModeSupport;
  position: absolute;
  transition: $speed;
  width: $toggle-indicator-size;
}

// The check mark is optional
.checkMark {
  fill: #fff;
  height: $toggle-indicator-size - 4;
  width: $toggle-indicator-size - 4;
  opacity: 0;
  transition: opacity $speed ease-in-out;
}

.toggle__input:checked + .toggle-track .toggle-indicator {
  background: $dark;
  transform: translateX($track-width - $track-height);

  .checkMark {
    opacity: 1;
    transition: opacity $speed ease-in-out;
  }
}

@media screen and (-ms-high-contrast: active) {
  .toggle-track {
    border-radius: 0;
  }
}
View Compiled

External CSS

  1. https://codepen.io/xirclebox/pen/c0f634aba1749dc2b7795f6dee888969.scss

External JavaScript

This Pen doesn't use any external JavaScript resources.