CSS Toggle Switch

It’s been a busy past few months at CloudFlare and now that we’ve recently begun a full-on overhaul of the website I thought I’d start sharing some little pieces I’ve been building.

The Toggle

Many of CloudFlare’s settings are literally turning features and services on or off, so Kevin (our designer), wanted to preserve the toggle concept (considering that it’s heavily used in our current UI). Stylistic changes aside, I wanted to make sure the toggle buttons were built and functioned significantly better than the existing ones; every one of the old toggles had to be instantiated via javascript, transitioned with jQuery animations, and were composed of 8 DOM elements each (!!).

The first toggle I came up with was built entirely from a <input type="checkbox" /> element, but sadly I came to find out that Firefox’s -moz-appearance: none doesn’t actually do anything, so you’re left with nothing but an oversized checkbox. It works wonderfully in WebKit, however. Chrome has also implemented CSS3 transitions for pseudo elements, which makes me wish even more that this solution worked.

The Final Markup

Once I got over the fact that I needed more than one DOM element to produce a toggle switch, I came up with the following snippet, which is composed of a label, the checkbox, and a span. I used the label to build the two states of the switch (using :before and :after) and the <span class="knob"> to control the toggle element. Basic CSS3 transitions work nicely across all (modern) browsers here, as the span is being transitioned, so there’s no need to worry about support for pseudo element transitions.

Downsides

One small gripe I have with this is hard coding the text into the CSS, which could cause some annoyances if you’re working with a multi-lingual site, but a simple solution is using CSS’s attr() function to pull the text from data attributes on the DOM element.

Unfortunately <=IE8 doesn’t support the :checked pseudo selector, so a small bit of JS is needed to detect the checked state for these browsers, if you plan to support them.

Original Code

The code above has been modified to allow it to be compiled in JSFiddle, but the original code I’ve written is below. I’m a big believer in ems/rems, so that is being used heavily across my projects. Maybe I’ll have to write up another post about how the units below are converted..

.switch{
	/* Hack to make Chrome round to .33334 */
	$switchWidth: 80.01px;

	background-color: $formBg;
	border: 1px solid $formBorderColor;
	color: $white;
	cursor: pointer;
	font-size: 0;
	height: $formInputHeight;
	overflow: hidden;
	margin: 0;
	padding: 0;
	position: relative;
	width: $switchWidth/$rem;

	@include user-select(none);
	@include appearance(none);

	&:after,
	&:before{
		background-color: $green;
		color: $white;
		content : 'On';
		font-size: $inputFontSize/$rem;
		line-height: $lineHeight;
		height: 100%;
		left: 0;
		padding: 6.75px/$rem 0;
		position: absolute;
		text-align: center;
		top: 0;
		width: 51%; /* This is so the rounded corners of the knob doesn't make things wierd */
	}

	&:before{
		background-color: $formBorderColor;
		content : 'Off';
		left: auto;
		right: 0;
		width: 50%;
	}

	.knob{
		background: darken($formBg, 3%);
		border: 1px solid $formBorderColor;
		border-bottom: none;
		border-top: none;
		display: block;
		font-size: $inputFontSize/$rem;
		height: 100%;
		left: -1px;
		position: relative;
		top: 0;
		width: ($switchWidth/2)/$rem;
		z-index: 2;

		@include border-radius($baseBorderRadius);
		@include transition(all 0.15s linear);

		&:before,
		&:after{
			border: 4px solid transparent;
			border-left-color: inherit;
			content : '';
			display: block;
			height: 0;
			left: 50%;
			margin-left: 2px;
			margin-top: -3px;
			position: absolute;
			top: 50%;
			width: 0;
		}

		&:before{
			border-left-color: transparent;
			border-right-color: inherit;
			margin-left: -10px;
		}
	}

	input{
		position: absolute;
		visibility: hidden;
	}

	input.checked + .knob,
	input:checked + .knob{
		left: 50%;
	}
}

Tagged: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *