HTML5 Development Center

Developed for you in part by
 
819-css3-toggle-switch

How to Create a Toggle Switch in CSS3

By | | CSS | CSS3 | HTML | HTML5 | Responsive Web Design | UX

You’ll find mobile-interface-like toggle switches in various places around the web but I wanted to improve existing examples.

toggle switch

Specifically, I wanted a solution which:

  1. progressively enhanced standard checkboxes
  2. did not use superfluous HTML tags or attributes
  3. supported input labels
  4. used only CSS without images or JavaScript
  5. used relative units so the controls are resizable/responsive
  6. had some slick animation
  7. ideally worked in a range of mobile browsers, and
  8. degraded gracefully so it remained usable in all browsers

View the demonstration page and the HTML/CSS code…

The HTML

We require a input checkbox and a label:

<div>
	<input type="checkbox" id="switch1" name="switch1" class="switch" />
	<label for="switch1">first switch</label>
</div>

The input has a class of “switch” assigned. This ensures we can retain normal checkboxes should we need them.

HTML purists will be horrified to see a wrapper div but it’s only necessary if you require two or more toggle switches — you cannot have more than one switch (or further labels) in the the same parent container. Besides, you’ll probably need div wrappers to separate form elements anyway.

The HTML will render well in most browsers with minimal styling. IE6, 7 and 8 users will see this:

toggle switch

The CSS

Now for the interesting part. First, we’ll hide the input box using a negative margin — this can be preferable to display:none which often disables it on mobile devices:

input.switch:empty
{
	margin-left: -999px;
}

You may have seen the :empty selector used in my recent post, How to Create a Responsive Centered Image in CSS3. It only matches elements which have no children but, since it’s not supported in IE8 and below, those browsers won’t attempt to apply the styles.

Next, we’ll style the sibling labels of the input checkbox:

input.switch:empty ~ label
{
	position: relative;
	float: left;
	line-height: 1.6em;
	text-indent: 4em;
	margin: 0.2em 0;
	cursor: pointer;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

The main properties to note are position:relative, the text-indent which provides room for our switch, and the line-height which defines its height.

The toggle itself is created using :before and :after pseudo-elements for the colored background and the white switch accordingly:

  • both elements are absolutely positioned at the left-hand edge of our label
  • the white switch is set to a smaller size and has a left margin applied to align it on the background
  • a border-radius and inset box-shadow is applied to give some depth, and
  • a transition is defined for the animation.
input.switch:empty ~ label:before,
input.switch:empty ~ label:after
{
	position: absolute;
	display: block;
	top: 0;
	bottom: 0;
	left: 0;
	content: ' ';
	width: 3.6em;
	background-color: #c33;
	border-radius: 0.3em;
	box-shadow: inset 0 0.2em 0 rgba(0,0,0,0.3);
	-webkit-transition: all 100ms ease-in;
	transition: all 100ms ease-in;
}
input.switch:empty ~ label:after
{
	width: 1.4em;
	top: 0.1em;
	bottom: 0.1em;
	margin-left: 0.1em;
	background-color: #fff;
	border-radius: 0.15em;
	box-shadow: inset 0 -0.2em 0 rgba(0,0,0,0.2);
}

Finally, when the checkbox is checked, we move the switch to the right-hand edge and change the background color:

input.switch:checked ~ label:before
{
	background-color: #393;
}
input.switch:checked ~ label:after
{
	margin-left: 2em;
}

View the demonstration page and the HTML/CSS code…

Everyone likes a toggle switch! Please use the code however you like. A link back to this article or a “hey Craig, that’s awesome/awful” tweet is appreciated.

Craig Buckler

Craig is a Director of OptimalWorks, a UK consultancy dedicated to building award-winning websites implementing standards, accessibility, SEO, and best-practice techniques.

More Posts - Website

{ 27 comments… read them below or add one }

Paul May 12, 2013 at 10:42 pm

I don’t know how I can use this, but it looks pretty cool. It seems that some day it may come in handy. I don’t have time to go over the lesson, but if I find a use for it some day I certainly will. I love to get these tips in the email and bookmark them. One more than one occasion I have bookmarked such a tip as this one and went back found it and used in on a website. Thanks for the cool tips.

Paul

Reply

Antonis May 11, 2013 at 11:23 pm

No js, only css….. that was AMAZING !!!

Reply

Sai Pc May 10, 2013 at 8:08 am

You know, i’ve been visiting sitepoint for a very long time..and EVERY single time,craig, you amaze me with something totally out of the box..:O :O who would’ve thought of toggle switches using pure css? you are just plain brilliant :)

Reply

Craig Buckler May 11, 2013 at 12:48 am

Thanks Sai. I’m sure I wasn’t the first person to do it, but most solutions I’ve seen use superfluous tags or JavaScript.

Reply

mahsa May 9, 2013 at 11:49 pm

thanks very very very GOOD!

Reply

Dale Rodgie May 9, 2013 at 4:34 pm

The toggle switches don’t work on my iPad. They display correctly but you can’t turn them on or off. I’m using the iPad 1 running iOS 5.1.1 and viewing the demo page – http://cssdeck.com/labs/full/css3-toggle-switch

Reply

Craig Buckler May 10, 2013 at 1:13 am

Hmm. There does appear to be an issue with older webkit browsers which is a shame. It sounds as though touching a label doesn’t toggle the checkbox. Can anyone confirm that? Is there a solution other than resorting JavaScript?

Reply

Susan May 9, 2013 at 12:41 pm

Why not use a fieldset instead of a division? More accessible and would work for purists.

Reply

Craig Buckler May 10, 2013 at 1:16 am

Fieldsets are normally placed around groups of related input boxes, e.g. a login form would have the email, password, a “keep me logged in” checkbox and the submit button. I’ve used a div because it’s separating individual fields. While they’re not strictly necessary, styling form controls is far simpler and more likely to work in all browsers.

Reply

Anthony Cartmell May 9, 2013 at 11:01 am

How about dispensing with the <div> by doing:

<label for="switch1">
 <input type="checkbox" id="switch1" name="switch1" class="switch" />
first switch</label>

Reply

Richard Czeiger May 9, 2013 at 4:49 pm

Craig – Anthony’s syntax is how I usually like to mark up checkboxes. Can you supply a tweaked version of your code to make this work?

Reply

Richard Czeiger May 9, 2013 at 5:08 pm

Actually just sticking a SPAN around the text of the label and changing the CSS references of ‘label’ to ‘span’ does the trick nicely. It’s a **little** extra markup but means I don’t have to rework previously written css!

Craig Buckler May 10, 2013 at 1:23 am

I did try label as a container but it won’t work. Until CSS supports parent selectors, it’s not possible to style the label based on the the :checked state of the input.

Richard – the span method should work, but isn’t it simply a div by another name?

Richard May 9, 2013 at 9:48 am

One accessibility problem – the focus indicator isn’t visible which makes these difficult to use for sighted keyboard only users.

Reply

Craig Buckler May 9, 2013 at 11:02 am

That’s a good point since labels never normally have a focus style – although I’m sure one could be applied. That said, it’s really hard to see the focus indicator around a standard checkbox.

Reply

Richard May 10, 2013 at 4:36 am

It is generally better to go beyond the default focus indicator, I often find it hard to see, and not just around a checkbox, and I have good vision (with my glasses on anyway).

Richard May 10, 2013 at 4:40 am

It is also worth pointing out that an indicator of red or green to show whether an option is on or not, wouldn’t be enough by itself, because of colour blindness issues. Admittedly the left or right status of the button may make it clear, but I am not sure everyone would see that as being obvious. From a usability point of view, more is needed, for example the on/off text in the iPad/iPhone example posted in the comments.

Craig Buckler May 10, 2013 at 5:33 am

Yep – you’re definitely right and I’ve slapped myself for having my head turned by wanton CSS animation without considering accessibility. I’m going to see if there’s an easy way to do it without adding further elements.

Nosnetrom May 9, 2013 at 8:44 am

Brilliant!

Reply

Craig Buckler May 10, 2013 at 1:23 am

Thank you!

Reply

Andy Kirk May 8, 2013 at 11:37 am

Hi Craig, thanks for the article.

I’ve just been working on the same thing myself. More closely based on the iPad/iPhone switch at the moment, but if course, it can be styled differently:

http://gridlight-design.co.uk/demos/switch.html

Any thoughts or feedback?
Cheers

Reply

Craig Buckler May 9, 2013 at 12:36 am

Very nice — I especially like the radio buttons.

I see you’ve styled the label itself. Is it still possible to have another text label which doesn’t get styled? And is there a reason you’ve used a hidden input above the checkboxes?

Reply

Andy Kirk May 9, 2013 at 12:45 pm

Thanks :-)

Usually on iPad the labels for what the toggles *are* don’t usually activate anything so I was just recreating that – as in that you could add the *actual* label outside of the label element. I guess it may be possible to add an extra span but I haven’t tried yet, and I’m not sure it’ll break down so well.

The hidden input is just a standard trick to make sure a value (0) is submitted if the checkbox (value 1) isn’t checked. Otherwise no value for that named element will be submitted. It isn’t anything to do with the actual CSS technique :-)

Craig Buckler May 10, 2013 at 1:28 am

Thanks Andy.

I’ve never considered that hidden input method before, but it’s a very interesting solution. I think I may need to write a short article about it! I’ll cite you as the reference…

Andy Kirk May 10, 2013 at 6:33 am

Hi again,
I can’t reply to your deeper reply, so just replying here instead.
Thanks for the offer of citing me for the hidden input trick. I didn’t invent it though – I’ve no idea who did.
I first came across it in the Zend Framework a few years back and I’ve been using it ever since :-)

Paul May 11, 2013 at 3:19 pm

Nice, this works on Android 4.1, which Craig’s unfortunately doesn’t. Props to both you!

Reply

Craig Buckler May 12, 2013 at 3:38 am

Hi Paul. I have a follow-up post coming soon which suggests some improvements and explains the Safari/Android issue. Incidentally, you should find it works on Chrome for Android – it’s only the stock browser which causes problems because it has an older webkit engine.

Leave a Comment