Accessible css / js switch

by ian_smithz

HTML

<link rel="stylesheet" href="https://www.screwfix.com/assets/css/sfx.min.css">
<br> <br>

<label class="check-switch" for="switch_1">
  <input id="switch_1" type="checkbox">
  <span aria-hidden="true"></span>
</label>

<!-- Some screen reader combinations reported to be buggy with role="switch" - you can just use checkbox version -->
<!--
<label class="check-switch" for="switch_2">

  <input id="switch_2" data-check-switch="" role="switch" type="checkbox">
  <span aria-hidden="true"></span>
</label>
-->

CSS

/* containing label */
.check-switch {
	padding: .5em;
  margin: 0 3em;
	position: relative;
}

.check-switch:hover {
	box-shadow: 0 0 2px 1px #2196f3;
}

/* using the before/after pseudo elements of the span to create the "switch" */
.check-switch span:before,
.check-switch span:after {
	border: 1px solid #565656;
	content: "";
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
}

/* styling specific to the knob of the switch */
.check-switch span:after {
	background: #fff;
	border-radius: 100%;
	height: 1.5em;
	right: 1.5em;
	transition: right .1825s ease-in-out;
	width: 1.5em;
}

/* styling specific to the knob "container" */
.check-switch span:before {
	background: #eee;
	border-radius: 1.75em;
	height: 1.75em;
	right: .25em;
	transition: background .2s ease-in-out;
	width: 2.75em;
}

/* hide the actual checkbox from view, but not from keyboards or ATs.
   Instead of standard visually hidden styling, instead set opacity to
   almost 0 (not zero for ChomeVox legacy bug), pointer-events none, and
   then set to full height/width of container element so that VO focus
   ring outlines the component, instead of a tiny box within the component
*/
.check-switch input {
	height: 100%;
	left: 0;
	opacity: .0001;
	position: absolute;
	top: 0;
	width: 100%;
}

.check-switch input:not([role="button"]) {
	pointer-events: none;
}

.check-switch input:focus + span:before {
	  outline: #a9a9a9 dotted 2px;
    outline-offset: 1px;
}

/* change the position of the knob to indicate it has been checked*/
.check-switch input:checked + span:after {
	right: .25em;
}

/* update the color of the "container" to further visually indicate state */
.check-switch input:checked + span:before {
	background: #2196f3;
}

/* 'color in' the switch knob in high contrast mode by giving it
   a large border */
@media screen and (-ms-high-contrast: active) {
	.check-switch span:after {
		background-color: windowText;
	}
}

JavaScript

(function ( w, doc, undefined ) {
	'use strict';
	var A11YswitchCheck;

	A11YswitchCheck = function ( ) {
		/**
		 * Author: Scott O'Hara
		 * Version: 0.1.0
		 * License: https://github.com/scottaohara/a11y_styled_form_controls/blob/master/LICENSE
		 */
		var el;

		/**
		 * Initialize the instance, run all setup functions
		 * and attach the necessary events.
		 */
		this.init = function ( elm ) {
			el = elm;
			setRole ( el );
			attachEvents ( el );
		};

		/**
		 * Check default state of element:
		 * A toggle button is not particularly useful without JavaScript,
		 * so ideally such a button would be set to hidden or disabled, if JS wasn't
		 * around to make it function.
		 */
		var setRole = function ( el ) {
			if ( el.getAttribute('type') === 'checkbox' ) {
				el.setAttribute('role', 'switch');
			}
			else {
				console.error(el.id + ' is not a checkbox...')
			}
		};

		/**
		 * Attach keyEvents to toggle buttons
		 */
		var keyEvents = function ( e ) {
			var keyCode = e.keyCode || e.which;

			switch ( keyCode ) {
				case 13:
					e.preventDefault();
					e.target.click();
				break;
			}
		};

		/**
		 * Events for toggle buttons
		 */
		var attachEvents = function ( el ) {
			el.addEventListener('keypress', keyEvents, false);
		};

		return this;
	}; // A11YswitchCheck()

	w.A11YswitchCheck = A11YswitchCheck;
})( window, document );