How to Create a Toggle Switch in CSS3

Tweet

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Andy Kirk

    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

    • http://www.optimalworks.net/ Craig Buckler

      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?

      • Andy Kirk

        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 :-)

      • http://www.optimalworks.net/ Craig Buckler

        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

        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

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

      • http://www.optimalworks.net/ Craig Buckler

        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.

  • http://nosnetrom.com Nosnetrom

    Brilliant!

    • http://www.optimalworks.net/ Craig Buckler

      Thank you!

  • http://accessibleweb.eu Richard

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

    • http://www.optimalworks.net/ Craig Buckler

      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.

      • http://accessibleweb.eu Richard

        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).

      • http://accessibleweb.eu Richard

        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.

      • http://www.optimalworks.net/ Craig Buckler

        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.

  • http://www.fonant.com Anthony Cartmell

    How about dispensing with the <div> by doing:

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

      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?

      • Richard Czeiger

        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!

      • http://www.optimalworks.net/ Craig Buckler

        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?

  • Susan

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

    • http://www.optimalworks.net/ Craig Buckler

      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.

  • http://www.aziom.com.au Dale Rodgie

    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

    • http://www.optimalworks.net/ Craig Buckler

      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?

  • mahsa

    thanks very very very GOOD!

  • Sai Pc

    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 :)

    • http://www.optimalworks.net/ Craig Buckler

      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.

  • http://www.fuzzfree.com Antonis

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

  • Paul

    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

  • http://muraliprashanth.me Murali Prashanth

    awesome! nice tutorial

  • http://www.zhuxinyong.com 楔子

    hi,I am think you should try another selecter ” + ” replace ” ~ “,then you can include two or more toggle switches with one div ,this is the demo http://jsbin.com/ewivob/3/