Accessible, Custom Designed Checkbox and Radio Button Inputs Styled with CSS (and a dash of jQuery)

Posted by Maggie on 07/01/2009

Topics:

Styling checkbox and radio button inputs to match a custom design is nearly impossible because neither reliably supports basic CSS, like background colors or images; it's even a challenge to get the margins to appear consistently across browsers. To remedy this we developed a concise jQuery plugin based on progressive enhancement that leverages an input element's built-in functionality and accessibility features and works in all modern browsers without added markup or mandatory CSS classes.

In the past few years web application interface designs have evolved from flat metal gray to having rich color palettes and dimensional background images thanks to the adoption of web standards and advanced CSS techniques. Form checkboxes and radio buttons have lagged behind, though, because only a few browser vendors have built in support for styling these elements with CSS, and inconsistently at that. A quick search turned up several workaround scripts that cleverly use a combination of JavaScript and CSS to apply a custom skin to these form elements, however they sometimes do more work than we need them to by inserting replacement markup, or requiring that specific classes be assigned to every applicable form element.

When considering how to build our own customized input script, we set out to do as little as possible — on their own, checkboxes and radio buttons capture data and display feedback, and we wanted to use that native functionality and not reinvent it using JavaScript. If we let the user interact with the inputs as expected, we'd only have to apply a lightweight script to layer on visual enhancements for richer feedback. And by keeping our hands off the inputs' native functionality, we were able to preserve their inherent keyboard accessibility and avoid the need to use ARIA attributes since a screen reader is still interacting with the native form element.

Update: We've received a lot of thoughtful comments on this technique, one of which stood out: what happens when images aren't available? In our first pass at this widget, we hid the input element by positioning it off the page, and then styled the label element with background images that mapped to click states (unchecked, hover, checked) -- so it became an unusable input without images. We've since re-factored the markup and script to ensure that the widget works even when images don't load. Instead of positioning the input off the page, we've positioned it directly behind the styled label, so if the label's background image fails, the input remains visible. And because the label and input share a click event, when the user clicks the transparent label over the input, the input updates accordingly.

Demo

When JavaScript is enabled, users see custom input checkboxes and radio buttons as shown below; when users are browsing with JavaScript disabled or with a screen reader, they interact with standard, unstyled input elements.

jQuery CustomInput plugin demo by Filament Group

Markup

We start with basic HTML for each input that follows web standards conventions:

  • assigned a unique id and value to each input
  • paired the input with a label element
  • included a "for" attribute on each label that references the preceding input's id

Each radio button input also needs a common name attribute to group it with a set.


<form>	
	<fieldset>
		<legend>Which genres do you like?</legend>

		<input type="checkbox" name="genre" id="check-1" value="action" />
		<label for="check-1">Action / Adventure</label>
		
		. . .
	</fieldset>		
	<fieldset>
		<legend>Caddyshack is the greatest movie of all time, right?</legend>

		<input type="radio" name="opinions" id="radio-1" value="1" />
		<label for="radio-1">Totally</label>

		. . .
	</fieldset>
</form>

Pairing the inputs and labels correctly is essential to how this plugin works. As stated in the HTML spec, "When a LABEL element receives focus, it passes the focus on to its associated control." Browsers have standardized this behavior so that when you click a label, the click is passed on to the input — in other words, the label and input act as a single element when marked up this way. Because we don't have to interact with the input directly, we can hide it from view with CSS and apply styles to the label to make it look like a customized checkbox or radio button.

When the page loads, the plugin script finds each input/label pair and wraps it in a div. Each wrapper div is assigned a class to it based on the type of input it contains:


<div class="custom-checkbox">
	<input id="check-3" type="checkbox" value="epic" name="genre"/>
	<label class="" for="check-3">Epic / Historical</label>
</div>

Styles

First, we absolutely positioned the input and label pair so that we could layer the label over the input, like a mask. For this to work, we relatively positioned the wrapper div to contain the input and label:


/* wrapper divs */
.custom-checkbox, .custom-radio { position: relative; }
	
/* input, label positioning */
.custom-checkbox input, 
.custom-radio input {
	position: absolute;
	left: 2px;
	top: 3px;
	margin: 0;
	z-index: 0;
}

.custom-checkbox label, 
.custom-radio label {
	display: block;
	position: relative;
	z-index: 1;
	font-size: 1.3em;
	padding-right: 1em;
	line-height: 1;
	padding: .5em 0 .5em 30px;
	margin: 0 0 .3em;
	cursor: pointer;
}

Next, we styled each type of label (checkbox and radio button) with a background image — we used an image sprite for all states: default, hover, and checked:


.custom-checkbox label {
	background: url(images/checkbox.gif) no-repeat; 
}

.custom-radio label { 
	background: url(images/radiobutton.gif) no-repeat; 
}

And added classes for hover and checked states that repositioned the background sprite accordingly. We also included a class for the "focus" state for keyboard users.


.custom-checkbox label, .custom-radio label {
	background-position: -10px -14px;
}

.custom-checkbox label.hover,
.custom-checkbox label.focus,
.custom-radio label.hover,
.custom-radio label.focus {
	background-position: -10px -114px;
}

.custom-checkbox label.checked, 
.custom-radio label.checked {
	background-position: -10px -214px;
}

.custom-checkbox label.checkedHover, 
.custom-checkbox label.checkedFocus {
	background-position: -10px -314px;
}

.custom-checkbox label.focus, 
.custom-radio label.focus {
	outline: 1px dotted #ccc;
}

Script

Because the label-input association takes care of clicking the hidden input for us, we only had to write a really simple jQuery plugin that appends a class to each input on hover, on focus, and on click:


jQuery.fn.customInput = function(){
	$(this).each(function(i){	
		if($(this).is('[type=checkbox],[type=radio]')){
			var input = $(this);
			
			// get the associated label using the input's id
			var label = $('label[for='+input.attr('id')+']');
			
			//get type, for classname suffix 
			var inputType = (input.is('[type=checkbox]')) ? 'checkbox' : 'radio';
			
			// wrap the input + label in a div 
			$('
').insertBefore(input).append(input, label); // find all inputs in this set using the shared name attribute var allInputs = $('input[name='+input.attr('name')+']'); // necessary for browsers that don't support the :hover pseudo class on labels label.hover( function(){ $(this).addClass('hover'); if(inputType == 'checkbox' && input.is(':checked')){ $(this).addClass('checkedHover'); } }, function(){ $(this).removeClass('hover checkedHover'); } ); //bind custom event, trigger it, bind click,focus,blur events input.bind('updateState', function(){ if (input.is(':checked')) { if (input.is(':radio')) { allInputs.each(function(){ $('label[for='+$(this).attr('id')+']').removeClass('checked'); }); }; label.addClass('checked'); } else { label.removeClass('checked checkedHover checkedFocus'); } }) .trigger('updateState') .click(function(){ $(this).trigger('updateState'); }) .focus(function(){ label.addClass('focus'); if(inputType == 'checkbox' && input.is(':checked')){ $(this).addClass('checkedFocus'); } }) .blur(function(){ label.removeClass('focus checkedFocus'); }); } }); };

Usage

Simply call the customInput() method on any input element or group of elements (more on using jQuery):


$('input').customInput();

Download the code

This plugin script requires jQuery's core library, available for download at jquery.com or link directly from Google's code repository. Download the plugin script, customInput.jquery.js, and feel free to use it in your projects (it's dual licensed under the MIT and GPL open source licenses; refer to author's notes).

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site

Comments

I like this approach for radios & checkboxes customization, it seems to be very lightweight, however i noticed that the checkboxes need a different visual state whenever they’re focused, which is necessary when you want to control them with the keyboard.

Comment by Jonathan Vitela on 07/02  at  01:16 PM

Nice rendering (should i say as always ;)), hope to see something like that integrated in jquery-ui in a near future to get this consistent with ui-themes.
As a side note it would be nice to also add a different background for checked input when they’re hovered.

Comment by malko on 07/02  at  01:23 PM

Filament Group has done it again! Nice work.

Comment by Marc Grabanski on 07/02  at  02:49 PM

These custom inputs are pretty. I agree with Jonathan Vitela to add a different visual state to checkboxes and radio buttons when focused via the keyboard. Good job!

Comment by Rubens Mariuzzo on 07/02  at  03:00 PM

@Jonathan @Rubens Good feedback. The keyboard focus feedback is now updated to be lot clearer now—we added a dotted outline and flip the form element to the hover state.

Comment by Todd (Filament) on 07/02  at  04:18 PM

Very nice, simple. Good effort.

Comment by redsquare on 07/02  at  04:34 PM

I opened up Firebug and changed one of the labels to be multiple lines. I thought I would try it out since I’ve had issues in the past with multiple lines and using sprites for the different states.  Having long labels may be an edge case but, I think, should also be accounted for.

Screenshot: http://screencast.com/t/Jvse61XPO

Apart from this, your method seems to be the best that I have used or found elsewhere. Good work!

Comment by Tony on 07/02  at  05:03 PM

Thanks Tony,
That’s definitely something to watch out for any time you’re using sprites. If you need this technique to work with really tall labels, the easiest way to “fix” it would be to space out the sprite images far enough to accommodate the label height, but of course that will always come with limitations. You cold also make the sprite horizontally spaced instead if that fits your design.
The other way would be to use jQuery to append a span element to the label, and then you could style that span with a width and height that will never grow, and put the background image on it instead of the label. This approach is more bulletproof I suppose, but we wanted to be as unobtrusive as we could with this script, so we decided against markup additions.

We may update this with a taller sprite though when we get time.

Comment by Scott (Filament) on 07/02  at  05:16 PM

Top notch once again guys!  Great work!

Comment by Jonathan Sharp on 07/02  at  05:17 PM

@Tony We just tweaked the image sprite to add an extra 100px between icons. Like Scott said, this approach isn’t as bullet-proof as adding an extra element for the icon but we like the simplicity in this case. The extra spacing should cover most reasonable cases and you can always adjust as needed for your project.

Comment by Todd (Filament) on 07/02  at  08:21 PM

Awesome! I’m glad to see that you guys are so open to and quick to respond to feedback.  In my current job, I have learned that you can never make any assumptions about data (especially the length of text and such). Thanks for your great work.

Comment by Tony on 07/02  at  08:26 PM

Looks good, but it presents an inconsistent user experience when a checked input is hovered over (or tabbed to) - i.e. it requires a new, additional state which represents checked AND hovered, which should show the green (hover) border around the input, in addition to the check mark / dot.

Please implement this.

Comment by Marcus Tucker on 07/05  at  09:10 PM

If the browser can not display images or they are off then inputs will be inaccessible. The checkbox and radio button will not be visible.

Comment by Cezary Tomczyk on 07/11  at  04:51 PM

Very nice! This is similar to the project Uniform (http://pixelmatrixdesign.com/uniform/demo/demo.html). Any plan to support changing file inputs?

Comment by Matthew Irish on 07/23  at  05:35 AM

What do you think of these modifications to the jquery plugin?

jQuery.fn.customInput = function() {
$(this).each(function(i) {

var input = $(this);

var radio = input.is(’:radio’);

// get the associated label using the input’s id
var label = input.next(’label’);

// necessary for browsers that don’t support the :hover pseudo class on labels
label.hover(
function() { $(this).addClass(’hover’); },
function() { $(this).removeClass(’hover’); }
);

if (input.attr("checked")) label.addClass(’checked’);

if (radio) {
// find all inputs in this set using the shared name attribute
var otherRadiosInGroup = $(’input[name=’ + input.attr(’name’) + ‘]’).not(’#’ + this.id);

input.click(function() {
otherRadiosInGroup.next(’label’).removeClass(’checked’);
label.addClass(’checked’);
});
}
else {
input.click(function() {
label.toggleClass(’checked’);
});
}

input.focus(function() { label.addClass(’focus’); })
.blur(function() { label.removeClass(’focus’); });

});
};

Comment by Gabriel on 07/25  at  09:04 PM

I love this idea… I extended it by getting rid of the div wrapper around the inputs. Now you only need the standard markup without any classes. You can check out a demo here:

http://www.paradimeweb.com/skinInput/

Please let me know what you think?

Thanks guys,

Comment by Gabriel on 07/25  at  10:33 PM

Wonderful.  I second the request to have this integrated into jQuery UI for theme support.  It’s easy enough to approach this solution using existing classes for checkboxes but the lack of rounded-corners support in IE makes it difficult to do for radio buttons.  Full-fledged glyphs would help out a lot (and require less markup/DOM manipulations)!

Comment by Ken Browning on 07/31  at  12:01 AM

Nice.... very nice!!!

Tks

Comment by Vinidog on 08/03  at  05:21 AM

This is absolutely great. Thank you so much for working this out.

Fileuploads and normal Textfields seem to be the only basic things missing in the for stylable themeroller compatible form elements.

Comment by tronics on 08/09  at  11:20 PM

Just wanted to send a thanks to Filament Group for creating such an easy to use plugin.. and Gabriel for simplifying it even further. It is working for me like a dream so far!

Comment by Monica LaCasse on 08/10  at  06:22 PM

The heading of the article is misleading, this method of checkbox replacement is not accessible.

Comment by michael on 08/14  at  08:51 AM

The code is really nice though - I think all it would need to become accessible is to move some of the css to be added by the plugin itself, that way if script is off the normal checkbox/radios just come up.

For anyone looking for a similar but accessible solution try
http://plugins.jquery.com/project/radiobutton-checkbox-replacement

Comment by michael on 08/14  at  08:55 AM

@michael: Could you explain a scenario where this widget is not accessible? It makes use of the native HTML input (check/radio) element, positioned out of view, so users on assistive technology actually interact with the input itself rather than other markup acting as a proxy to that input. It appears to be somewhat similar to the technique you linked to above, except we’re just toggling classes on the label element based on the state of the input itself (instead of using an extra span). As far as we can tell, this is completely accessible, and we’ve tested on a couple screen readers to make sure (but please let us know what you’ve found).

As mentioned in the comments above, one scenario where accessibility may be compromised would be for visual users who have images disabled, because the background images are necessary for the visual user experience. We have updated the script locally to fix this issue by positioning the input directly behind the image icon. That way if the image fails to load, the native input can be used in its place. When we get time, we’ll try to update this article with that revised script.

In general though, we’re curious where you think this widget may cause problems with accessibility, so please let us know! Thanks.

Comment by Scott (Filament) on 08/14  at  04:23 PM

@Scott
Other than the situation you spoke off there is one more.
When javaScript is turned off the radio and checks are positioned off stage and your just left with some round and square boxes with no functionality. I like the updated method you speak of and although I have not seen this yet I think it may act the same when javascript is off.

A quick fix for the updated script could be.
Remove this from the css ‘.custom-radios label’ class
background: url(check-radio.gif) no-repeat 0 0;
at beginning of script
$(’.custom-radios label’).css({’background’ : ‘url(check-radio.gif) no-repeat 0 0’});

This way if javaScript is turned off the images wont even load and you’ll get normal check and radios that function.

What do you think?

Comment by michael on 08/19  at  02:26 AM

@ michael: oh. That’s odd. Thank you for pointing out that the classes are in the markup at page load. These should definitely be added with JS so there’s a distinct difference in experience when JS is unavailable.
We’ll try to upload the new script as soon as we can!

Comment by Scott (Filament) on 08/19  at  03:55 PM

Thanks again to everyone for the helpful feedback!  We just posted an update to the plugin script and CSS that fixes a problem mentioned by Cezary Tomczyk—the widget as we had originally structured it failed when images weren’t present.  So we re-factored the script to work whether or not images load. And, to Michael’s point, we also made sure that the classes to position the inputs are applied by the script, not written into the markup served with the page, so that they remain in place when JS isn’t available.  Check out the updated article for details.

Comment by Maggie (Filament) on 08/19  at  10:23 PM

Awesome this is now perfect by well… my standards. Thanks for taking the comments/suggestions on board, and posting an updated script.

Comment by michael on 08/20  at  01:47 AM

when the checkbox is INSIDE the label, this script kills both :S

Comment by Luka Kladaric on 08/20  at  11:46 AM

@Luka Kladaric:  I can imagine a few cases where nesting inputs in labels would simplify the markup or a related script, but we avoid marking up forms that way for a few reasons: 

* semantically it doesn’t make sense for an input to be a child of its label,
* the “for” attribute associates labels to their form fields, so nesting really isn’t necessary, and
* it’s easier to style them as separate elements

For these reasons we don’t plan on updating the script to work with nested input/label pairs, but feel free to update it to suit your project—it’s open source under the MIT and GPL licenses.

Comment by Maggie (Filament) on 08/20  at  04:12 PM

for some reason, <input type="checkbox" checked="” /> will be detected as “checked” by your plugin.

Strictly speaking, it should be checked either as:

<input type="checkbox" name="vehicle" value="Car" checked="checked" />

or
<input type="checkbox" name="vehicle" value="Car" checked />

The following should not be checked:

<input type="checkbox" name="vehicle" value="Car" checked="” />

Comment by pixeline on 08/20  at  06:21 PM

Hey

What should I do to have the inputs horizontally aligned instead of vertically?

Thanks in advance

Comment by RS71 on 08/20  at  09:21 PM

@Maggie: okay, thanks for the clear response.. I do think it should work, or at least skip and not mangle the mentioned case, but it’s still a great plugin as-is

Comment by Luka Kladaric on 08/20  at  11:16 PM

@ luka.
The powers in your hands. Just apply a class to the elements that you either want or don’t want to have this functionality applied and update the script call accordingly.

@RS71 -
remove display:block; and add float:left; to the below class
“.custom-checkbox label, .custom-radio label”
I haven;t tested it but should be ok and get you started.

Comment by michael on 08/25  at  06:11 AM

@pixeline:  the behavior you’re seeing—where inputs with the checked attribute set to checked="” are showing up as checked—is actually standard browser behavior, it’s not the plugin.  We agree that logically checked="” should register as unchecked, but apparently browsers only look for the presence of the attribute (we think this may be the case because it only accepts one value).  It looks like the safest bet is to make sure there are no empty checked="” attributes in your code.

Comment by Maggie (Filament) on 08/28  at  05:15 PM

First of all, thank you for the script. It’s small and flexible. I minimized the script and css, because I only need radio button. And made some changes. If anyone wants these. Here it is:

Screenshot: http://yfrog.com/9hcustominputg
Source: http://jump.fm/SMERO

Comment by Sevil YILMAZ on 08/29  at  01:34 AM

Hello.

Your script does not work in Internet Explorer 8, if the attribute name is array
look:

<input type="checkbox" name="elementname[key1]" />
<input type="checkbox" name="elementname[key2]" />
<input type="checkbox" name="elementname[key3]" />
<input type="checkbox" name="elementname[key4]" />

Write about it in the documentation or fix the bug, so people do not suffer.

Comment by Anton Kulakov on 09/15  at  09:40 AM

@Anton: If there is indeed a conflict with array-like name attribute values and this script, we welcome you to comment with a patch. Otherwise, when we have time, we will take a look and see if it’s something we can work around.

I understand that the practice of structuring name values in this manner is at least somewhat common among developers, but do you think the issue may have to do with IE8 itself and its conformance to the doctype you’re using? For example, the HTML 4 recommendation specifies which characters are allowed within a name attribute’s value, and that limited character set does not include square brackets. I’m not sure about other doctypes, but before we spend time on it, we’d appreciate if you could make sure the issue is actually a bug in our script, and not just the implementation in IE8 (however proper or quirky that may be).

And again, patches and constructive feedback are always appreciated.

Comment by Scott (Filament) on 09/15  at  04:15 PM

Nice Job Guys. Excellent and works great in I.E 6 ;)

Comment by Trevor Saint on 10/19  at  01:00 PM

Very Nice!! and just read in the Comments that it even works in IE6!! fantastic

Comment by Greg on 10/21  at  05:38 PM

Impressive option for forms. Thanks for putting all of this together and sharing. Much appreciated.

Comment by Jon Livingston on 10/21  at  05:42 PM

Nice script.

One thing I did notice is that the the skinned inputs don’t reflect any changes made to the values of the input fields using JavaScript.

Simple use case, I may have a form containing two radio options - “Schedule a Payment” or “Pay Now”. One of these options has an additional field associated with it where the user can enter a date or something.

If a user clicks the date field and inputs something, I want to auto-select the appropriate radio button for the user.  With this script applied, this is not reflected to the user.

Perhaps a listener on the state of original input would be a good idea?

Comment by Chris on 10/21  at  08:05 PM

Adding this additional Event listener seems to solve my problem:

input.change(function(){
if (input.is(’:checked’)) {
if (input.is(’:radio’)) {
allInputs.each(function(){
$(’label[for=’+$(this).attr(’id’)+’]’).removeClass(’checked’);
});
};
label.addClass(’checked’);
}
else { label.removeClass(’checked checkedHover checkedFocus’); }
});

Of course, when changing elements via JavaScript, you need to remember to trigger the change event.

Comment by Chris on 10/21  at  08:18 PM

Wow, this is one of those techniques which are clearly, radiantly, fantastically useful. Thank you for the write-up.

Comment by Jeremy Carlson on 10/22  at  05:46 PM

FAIL - Come on guys, I tried to tab through the page, and it got it wrong on the radio buttons. It tabs straight to the selected button and then off the form. How has this been tested for accessibility?

Comment by terry t on 10/22  at  11:39 PM

@terry t:  It seems you have a problem with native form behavior, as we didn’t do anything but style the checkboxes and radios; both sets of inputs rely on their native keyboard functionality.  In Firefox, for example, you can tab between checkboxes because each one accepts a value, but you can only tab *to* a set of radio buttons because the entire set only accepts a single value.  To make a radio button selection, you have to use the arrow keys after you’ve tabbed into the set.  Try turning off JavaScript and you’ll see that our example page works the same way whether the inputs are enhanced with styles or not.

Comment by Maggie (Filament) on 10/22  at  11:55 PM

Beautiful work guys.
Odd behaviour though in IE7: the page is long and has a lot of checkboxes. Clicking on the last one brings the focus on top page, scrolling abruptly upwards in one shot.
Doesnt do so if I disable the .custom-radio input attributes in the CSS (I believe absolute positioning could be the problem).

Comment by Manaus on 10/23  at  02:03 PM

@Manaus
I have been having the same problem, haven’t had a chance to look at the javascript to see why its doing that if something isn’t posted up sooner than i figure something out ill post a response!

Comment by Matt on 10/30  at  08:11 PM

Follow up i fixed the IE 7 bug for me by removing position: absolute; from .custom-checkbox input, .custom-radio input, and adding left: -20px to custom-checkbox label, .custom-radio label. This seems to have solved the issue for me.

Comment by Matt on 10/30  at  08:20 PM

Nice Job, Nice script ---- But… It doesn’t play well with others......  Incorporating this into pages using slideToggle produces less than desirable results in IE 6,7,8.

Specifically, this is more of a slideToggle issue with nested elements using position: absolute etc....  But it makes this wonderful tool - useless

Comment by Kevin on 11/19  at  06:04 PM

Great script! I do have 1 question regarding the radio button styles. Is it possible to have each of the radio buttons have a different style? For example: 1st Radio has a Green Check and the 2nd Radio has a Blue Check?

Comment by Stephen on 12/17  at  08:05 PM

@Stephen: sure, you can apply whatever classes you’d like to the labels to style them as you need.

Comment by Scott (Filament) on 12/17  at  08:10 PM

@Scott Thanks for the prompt response. I want to use 2 different background images for the .custom-radio label style. Without making a large amount of changes to your jQuery code I don’t see this being possible. Am I missing something here? Thanks!

Comment by Stephen on 12/17  at  10:35 PM

@Scott figured it out, thanks!

Comment by Stephen on 12/17  at  11:58 PM

thanks!

Comment by yyj on 12/30  at  05:25 AM

just a small bug report… line 63 should be:

label.addClass(’checkedFocus’);

Comment by Joshua Jabbour on 01/10  at  10:15 PM

I came across this after trying to solve a problem with my own similar solution. In my HTML, the text of the label is hidden so the user toggles the checkbox by clicking the “image” (really the label with a background image). The label contains a span that is hidden if images are on.

This works dandy in Safari, Chrome, etc., but in Firefox, clicking again after the first click highlights the text of the label rather than toggles the checkbox. Obviously, this makes my label-less button pretty much unusable in Firefox.

I noticed this is happening in your demo (I also replaced my script with yours to see if there was something I’d missed - same issue) as well. I feel like I solved this recently - the Firefox-selecting behavior - but can’t for the life of me remember how. Any thoughts?

Comment by Jennie on 01/12  at  04:44 AM

Great job, thanks for this code.
In your example in IE form legend tag has its default color - blue. It seems that IE needs extra color declaration. In Firefox legend color is inherited.

Comment by Krystian on 02/10  at  08:20 PM

thanks for this script. I found a problem though when radio fields have names like radio[id]. Fixed it enclosing line 26 with double quotes:
var allInputs = $(’input[name="’+input.attr(’name’)+‘“]’);

Comment by rachel on 02/15  at  02:19 PM

I’d like to rebut your statement, “Styling checkbox and radio button inputs to match a custom design is nearly impossible...”

I have come up with a pure CSS solution for styling radio and checkboxes, http://www.thecssninja.com/css/custom-inputs-using-css

Comment by Ryan Seddon on 02/19  at  09:45 AM

@Ryan: Thanks, that’s an interesting technique, and it’s similar to ours in that you’ve found a workaround to avoid styling the actual inputs (though yours doesn’t use any JS, which is nice). We usually aim to figure out a solution that works across all the common browsers. The CSS selectors you’re using won’t work in any versions of Internet Explorer, will they?

Comment by Scott (Filament) on 02/19  at  04:14 PM

Why is this no widget in jquery ui? I think, styling forms and make them look the same in all browsers is the most common wish for all developers.

Comment by Oliver on 03/09  at  02:15 PM

Your work, is it the base of jquery ui checkbox,radiobutton widget ?

Comment by blr on 04/05  at  01:25 AM

Great approach and such simpicity!!!

I need some help. I want to be able to use a sprite to display a different image for each radio button. So I added a class to each label such as “xs”, “s” and “m”. Then I added
this for each class:
.custom-radio LABEL.xs {
BACKGROUND-POSITION: 0px 0px;
}
.custom-radio LABEL.xs.hover {
BACKGROUND-POSITION: 0px -20px;
}
.custom-radio LABEL.xs.focus {
BACKGROUND-POSITION: 0px -20px;
}
.custom-radio LABEL.xs.checked {
BACKGROUND-POSITION: 0px -40px;
}
and it works in Safari 4 and Firefox 3.6.3 but guess what: not in IE8

I posted the sample here: http://www.johnrudolf7summits.com/test/radio.htm

Thanks in advance

Comment by Michael on 04/13  at  08:10 AM

Sorry,

I didn’t specify the error in the above post. On hover over “xs” or"s" the incorrect sprite “m” is displayed but only in IE. I’m just clueless as to how to fix this!

Comment by Michael on 04/13  at  08:17 AM

Any chance you can post up a .psd of the sprite you’re using for the checkboxes and radio buttons?  Would be awesome to change the green color to another color that might be in a project’s palette.

Comment by cancel bubble on 04/14  at  08:42 PM

Can you please post sample example using above features/functionality ?

Comment by Chital on 05/11  at  03:09 PM

Hello,
very pretty script you wrote, thank you !
I have one question : in my project, I have to display dialog boxes (formerly with jQuery.dialog(), now with nyroModal plugin), HTML content of these dialogs is generated by javascript. And in these dialogs, customInput isn’t called… (in the others parts of the website, customInput works perfectly)
Does anyone have an idea ? Do I have to call again customInput in every dialog I create or is there another, more elegant, solution?

Comment by AkaiKen on 06/18  at  10:58 AM

This was a great help, thanks for putting it out.

Comment by Bill on 07/07  at  11:31 PM

An issue arised while I placed checkboxes and radio buttons within dialog boxes. The appearance is fine but I am not able to select the check box. As I have found a small surface on the top left is working and makes selection. It doesn’t work properly. I couldn’t figure it out what’s wrong with it. I appreciate your help.

Comment by Alipix on 07/20  at  09:07 AM

Excellent script. Love this version. Will get my head into it in more detail and use for future projects.

Top effort chaps.

Thank you.

Trevor Saint

Comment by Trevor Saint on 07/20  at  01:12 PM

Thank you for this nice plugin,
is it possible to have a status for disabled checkbox and radio boxes??
Thank you

Comment by Reda on 07/21  at  09:42 PM

When you uncheck checkbox a focus class is still on this element, no matter a cursor is or isn’t above this checkbox. To solve this problem I’ve made a small modification:

label.hover(
function(){
$(this).addClass(’hover’);
if(inputType == ‘checkbox’ && input.is(’:checked’)){
$(this).addClass(’checkedHover’);
}
},
function(){ $(this).removeClass(’hover checkedHover focus’); }
);

I remove focus class when cursor is out of checkbox.

I hope this help;)

Comment by luke on 08/12  at  10:32 AM

@ Reda: We’ll be sure to include disabled state management when we release the rewritten version of this plugin from our book. Look for that update sometime soon!

@Luke:  The focus class should be present when the input is actually focused, which is a different state entirely than a mouse hover. We wouldn’t recommend removing the focus class on mouseout, as the states are largely unrelated. If you’re navigating the page with a keyboard, an input will retain its focus while checking and unchecking it. When possible, we prefer to match that native behavior to match user expectations.

Comment by Scott (Filament) on 08/12  at  06:36 PM

Thank you, looking forward to it ;)

Comment by reda on 08/12  at  06:44 PM

@Scott: Thank you for answer. I have to admit that you are right. I had a more complex form where states of one element depends on states of others and for a moment a thought modification that I made was a solution, but I was wrong. Still I got a really bad mess with states on radio buttons and checkboxes.
Now I’ve made some changes in my scripts to modify form element classes rather than modify plug-in itself and its works much better.

Thank you for sharing this plug-in.

Comment by luke on 08/13  at  10:40 AM

Hi,

I think there are some elements missing in the script pasted above on this page:

// wrap the input + label in a div
$(’
‘).insertBefore(input).append(input, label);

looks like it is missing the div generation. It seems to be okay on the example pages, apologies if that was pointed out in the text and I inadvertently skipped over it.

Comment by Tim on 08/21  at  02:07 AM

Any plans to make this work with Themeroller?

We’re doing everyhting in our app with jquery ui themes, and having to make a couple of extra gifs for each theme to match can turn into alot of photoshop drudgery :P

Thanks!

Comment by Charles on 09/01  at  09:44 PM

Any idea about the next rewritten version release date??
thanks

Comment by Reda on 09/04  at  04:26 AM

Hi, at first: I love this script!
Unfortunately I have a problem, not knowing how to solve it:
I use only the radiobuttons. There’s a list with about 100 persons and for each person 10 different states of the radio can be chosen. No problem for FF, but in IE7 (IE8 I don’t know yet) theres an alert questioning me, if I want to go on, because a script is slowing down the proceeding. Telling this alert twice (or some more times) to go on, finally all is presented well and works fine. Does somebody has an idea to solve this problem of performance? Thanks in advance!

Comment by elke on 09/13  at  06:07 PM

I added reset button, but its not working for me.
need help pls
thanx
sanjiv

Comment by sanjiv on 09/22  at  07:22 PM

@reda: I also needed the disabled state, and I made some tweaks to support it. You can find them here: http://www.coshima.com/s/stuff/checkbox/. Feel free to check and see if it works for you.

Comment by Craig Oshima on 09/22  at  11:28 PM

@Craig Oshima: thank you so much ;) i’ll use it very soon. didn’t have the courage to modify the code…
I’d like to ask a question that has nothing to do with this plugin, i just didn’t find the answer in any forums…
i’m creating a website that has a login popup exactly like in twitter.com and am using Ajax ($.post)
the probleme is that the page isn’t in https, that means the user is entering his email and password in an unsecured page. do you have a solution for that?
i found this plugin if it interests anyone but is it as reliable as ans SSL certificate?
http://www.jcryption.org/
Thank you so much for your help

Comment by Reda on 09/23  at  12:33 AM

Hi,
I want to give check all functionality to your script. But it is not working. I have set all checkboxes checked using their respective ids.

<input type=’checkbox’ id=’c1’ name=’c1’>
<input type=’checkbox’ id=’c2’ name=’c2’>

function checkAll()
{
for(i=1;i<=2;i++)

}

Comment by computerzworld on 10/01  at  09:05 AM

Continuing with my previous comment…

for(i=1;i<=2;i++)
{
document.getElementById(’c’+i).checked=true;
}

And I am calling this function on click of link. But checkboxes are not getting checked. Has anybody implemented this functionality? If yes then please share your reviews over here. Thanks..

Comment by computerzworld on 10/01  at  09:08 AM

Nice script but I’m having a heck of a time trying to get two different lists of radio buttons to style differently on the same page in IE6. It works fine in every other browser but in IE6 the first set of radio buttons is picking up the hover, focus and checked states of the second set even though they have very distinct classes. There’s also a checkbox further down on the same form picking up the same style as the last set of radio buttons even though I haven’t included a custom-checkbox class anywhere in the CSS. It’s driving me nuts.

Comment by Brad Shaw on 10/15  at  06:40 AM

@computerzworld: you can’t just set the checked property to true as you’ve discovered. You must also trigger the custom event updateState to update the display. The same goes for setting disabled state. I actually added a function to do take care of this:

jQuery.fn.checked = function(bool) {

// If no bool param, return value of first checkbox or radio
if (typeof bool == “undefined") {
if (this.length > 0) {
return !!this[0].checked;
}
return false;
}
// Otherwise set the checked attribute accordingly
else {
return this.each(function(i) {
var $this = $(this);
if ($this.is("input:checkbox,input:radio")) {
this.checked = bool;
$this.trigger("updateState");
}
});
}
};

FWIW, I’m probably going to end up swapping this out with the checkbox/radio that jQuery UI is working on, because I’m finding this solution a bit limited when you have various non-white or patterned backgrounds.

Comment by Craig Oshima on 10/15  at  04:50 PM

Oh, you would use the above like:

$("#c1").checked(true);
$("#c1").checked(); // returns true

@Brad Shaw:
I have the good fortune of being able to not worry about IE6 anymore. :-)

Comment by Craig Oshima on 10/15  at  04:55 PM

@Craig

Yeah, unfortunately IE6 still makes up close to 20% of our visitors so I haven’t got much choice in the matter. It’s got to work in that POS browser as well. It’s sad that IE is holding up the Internet, and soon people will be using 4 versions each with it’s own issues. Makes me want to cry.

Can you give me a more fleshed out example of how to make this script work with two distinct classes of radio buttons? In my case I have flags with $, £ and € symbols at the top for selecting currency and then big fat check boxes (radio buttons in disguise) in a list to select a product package.

Thanks!

Comment by Brad Shaw on 10/15  at  05:55 PM

@Brad Shaw: Wouldn’t this be exactly the time when you’d explain to your client that styling checkboxes is an enhancement that only works in modern browsers? Obviously you want the users to like the site, but IE6 users are not going to be seeing much in the way of sexy styled checkboxes anywhere else either. They don’t see the internet that you see, nor, I might suggest, is it important that they do. What’s important is that the site works for them. Their experience, at 20% market share, is certainly important. But identical? I would suggest not.

Just my $.02. I don’t plan to abandon IE6 to the wolves anytime soon, but I don’t think bending over backwards for it makes sense for anyone - the developer, the client, or the user.

Comment by Jeremy Carlson on 10/15  at  07:20 PM

@Jeremy: While I can appreciate the sentiments (I’m all for killing of IE6 ASAP), I’m pretty sure this is quite doable in IE6, I just need to figure out how to make it work. The single style demo here on filamentgroup.com works perfectly fine. What I am doing nearly works, I’m just missing something.

I have to disagree that it’s not important for IE6 users to receive the same web browsing experience wherever possible especially when talking about ecommerce sites and sales conversions. I can’t just disregard 20% of my customer base and relegate them to a inferior experience because of my disgust for IE6. Doing so could mean a very significant difference to my bottom line.

This isn’t bending over backwards, it’s just me not understanding how to get two custom classes working properly with this script, and I would appreciate a little help.

Comment by Brad Shaw on 10/15  at  07:42 PM

@Brad Shaw - I hear ya :) Do you have a sample to look at? I’m sure Craig’s your man here, but I’ve gotten curious ...

Comment by Jeremy Carlson on 10/15  at  07:53 PM

I’m fairly certain the problem is that IE6 is choking on my CSS. When I modified the Filament Group plugin, I made a number of modifications (described on my sample page); I didn’t *just* add the disabled state. One of those changes was to eliminate the original plugin’s “checkedHover” and “checkedFocus” CSS classes. Technically, they were unnecessary, and it allowed reducing the amount of code. That’s great for me, but it’s probably what breaks IE6. I see that Filament’s sample works fine, but even my basic sample is a bit wonky on IE6. When I get a chance, I’ll see if backing out that change fixes things. No promises though...it just seems the most likely issue.

Comment by Craig Oshima on 10/15  at  09:02 PM

Hey. The test page is here… http://www.deepbass.com/lmbp-form-demo/ and the page is buy-ion-extra.php to see the order form. Don’t want a live link to it from here. It’s meant to replace our live ordering form which isn’t so hot in comparison.

As you can see it’s working nicely in all other browsers other than IE6. Tested in FF, Chrome, Safari, IE7 & 8… there are a couple small cosmetic issues in Opera but it’s working right for the most part. Had CSS3 PIE installed for the rounded corners but it’s more trouble than it’s worth I think.

Anyways, if you’ve got any feedback whatsoever let’s hear it! Thank you.

Comment by Brad Shaw on 10/15  at  10:04 PM

Oh...you’re still using the original Filament Group plugin, so it definitely doesn’t have anything to do with any changes I made with my version of their plugin. I remember that IE6 had lots of issues with shifting background images, so that’s where I’d start looking.

(And I know I’ll be called a heretic, but since 20% of your audience warrants the extra effort, I’d consider building a custom solution that works for IE6 and not bother with trying to find a single solution that works for IE6 *and* more modern browsers. I think it would be quicker and easier to support two versions of this page than to 1) tear your hair out figuring out IE6’s woes and 2) polluting your main branch with hacks to get it working with a 10-year old browser that will only get less common over time. In its day IE6 was great, and you can easily implement this page & functionality for IE6...just not with modern code. Just my opinion, but I’m just not into that agony...I mean, challenge :-)

Comment by Craig Oshima on 10/16  at  12:38 AM

OK, thanks for the feedback. I’ve figured out what the problem is.

The code would write a custom class with a hover state for a radio button like this:

<label for="usd" class="usd hover">USD</label>

A custom class with a hover state for a radio button that might come later in the form might look like this:

<label for="product-1" class="product hover">Product 1</label>

With the “hover” class overriding the custom CSS of the previous one. What I would like to do is have the class amended on hover without the space between the two therefore becoming a unique identifier. Then I would write the CSS rule like:

.custom-radio label.producthover

instead of

.custom-radio label.product.hover

How could I modify the script to do this?

Comment by Brad Shaw on 10/17  at  12:45 AM

Basically addClass adds a space to the already existing class when adding the new one. Is there some way to make it amend the class on rollover without adding that space?

Comment by Brad Shaw on 10/17  at  02:37 AM

Yes and no. Yes, it’s possible to update the class to be “producthover”, but you must explicitly say addClass("producthover"). You can’t have <label class="product"> and expect addClass("hover") to make the class be “producthover”. Each class is distinct, and spaces is how they’re separated. In the original plugin’s code, you’ll see addClass("checkedHover") and removeClass("checkedHover"), so I’d look at that if you think this is the best solution. HOWEVER, for your case, I think it might be better to revisit the markup and CSS. You have 2 types of radios...instead of making them different by assigning classes (like “product") to the radio elements themselves, put the class on their containers. Then you can scope your CSS selectors accordingly, like:

.products .custom-radio label.hover {...}
.currency .custom-radio label.hover {...}

Just a thought…

Comment by Craig Oshima on 10/17  at  06:50 AM

The problem with the latter solution is that it would require a bunch more divs. Each currency button would be required to be wrapped in a div, which is in turn wrapped in a div by the script. Same with the product selection since there are 4 different classes of radio buttons in that.

I might modify the wrapper div that the script creates to enclose each input/label pairing in a variety of different ways but I don’t know how to do that.

Comment by Brad Shaw on 10/17  at  05:10 PM

Loving this solution…

That said.... (first praise, then criticism ;))
It looks like this has a usability-problem when used in a form with a reset-button…

Anybody (besides Sanjiv ;)) encountered this, and even better has found a solution/workaround for this?

Comment by ReLexEd on 10/20  at  08:34 PM

Almost certainly that problem is related to the fact that the custom “updateState” event is not triggered. To fix that, you’d want to trigger that event on all custom checkboxes/radios when the form is reset. (Personally, I rarely find Reset to be a useful function for users...hey! Advice, then criticism? ;-) )

Comment by Craig Oshima on 10/20  at  10:47 PM

Been looking for a way to way to create a simple voting form, using radio buttons and replace them with some images that are a little more palatable to look at should be perfect. Thanks for creating this.

Comment by Tyler Herman on 10/26  at  12:28 AM

Hey .
What should I do to have the inputs horizontally aligned instead of vertically? .
Thanks in advance

Comment by basur on 11/02  at  11:03 AM

I’m fairly certain the problem is that IE6 is choking on my CSS. When I modified the Filament Group plugin, I made a number of modifications (described on my sample page); I didn’t *just* add the disabled state.

Comment by sigara bırakma on 12/01  at  02:05 PM

Hi,

What about RTL e.g. <html dir="rtl">?

Comment by NajiCo on 12/03  at  09:02 AM

Hi,

It’s not working with RTL e.g. <html dir="rtl"> or when we use a panel with RightToLeft Horizontal alignment? Hope you can help in this issue.
Thankx in advanced.

Comment by Najico on 12/04  at  07:32 AM

Hi,
the plugin doesnt’ work with inputs having array (brackets) in the name attribute.

To fix this problem, I made some changes in the code:
I replaced
var allInputs = $(’input[name=’+input.attr(’name’)+’]’);

with this code:
var allInputs = $(’input[name="’+input.attr(’name’)+‘“]’);

by doing this, the name variable will not fail if the jQuery attribute contains array.
Hope, it can help.
Mike Plavonil

Comment by MikePlavo on 12/13  at  06:12 PM

Awesome plugin.
@MikePlavo:  thanks you for this tips.  It prevented me from a lot of headaches

Comment by Ethan on 12/16  at  12:45 AM

Hi, where is the images for checkbox and radio?

Comment by Mik on 01/24  at  06:56 PM

I was wondering if this script cancel the action onclick on the input

I used another script that does the same thing and it canceled the onClick action ive put on my input :(

Exemple: <input type="checkbox" name="checkbox" onClick="location.href=\’?fct=approuve&filename;=’.urlencode($file).’\‘“ />

Comment by Vincent Talbot on 01/25  at  09:12 PM

Correction:
Proper code for previous comment on disabled checkboxes

label.hover(
function(){
if(input.attr(’disabled’)) {
$(this).css({cursor: ‘default’});
} else {
$(this).addClass(’hover’);
if(inputType == ‘checkbox’ && input.is(’:checked’)){
$(this).addClass(’checkedHover’);
}
}
},
function(){ $(this).removeClass(’hover checkedHover’); }
);

Comment by Dave on 02/11  at  07:14 AM

Using Chrome on Windows 7, when clicking on a checkbox, the keyboard focus is not set to that selected checkbox. Thus, when pressing TAB, the keyboard focus starts with the first element on the window, not with the next following element after the previously selected checkbox.

The keyboard focus seems to work in IE and Firefox. In these browsers, if you have 5 checkboxes, click on checkbox 3, then press TAB, the next selected element in the window is checkbox 4. On Chrome it is checkbox 1.

Comment by Rudolf Bargholz on 02/24  at  09:27 AM

When is this going to make it’s way in to the jQuery UI?

Comment by Nathan on 02/25  at  10:46 AM

Great Tutorial but i having problems with IE 7, and i can’t figure out the reason because i did everything exactly like in the tutorial.
look at yhe bug: http://radioz2.co.cc/radioz2/test/ie7bug.aspx (if you don’t have IE 7 switch to compatibilty mode in IE8).
PLEASE HELP ME!.

Comment by tod.radioz on 03/09  at  04:07 PM

Why doesn’t it keep states? When I check a radio or checkbox and reload the page it doesn’t remember the states. Everything is unchecked on reload (F5). It works in FF 3.6.15 but not Chrome 10, IE8, Safari 5.0.4 (PC) and others. This is true in your demo (http://www.filamentgroup.com/examples/customInput/) as well.

Comment by Jens Hedqvist on 03/15  at  03:03 PM

I’ve found it. When the DOM is altered (the custom input and label is wrapped in a div.custom-checkbox) Firefox gets confused and is unable to track states. For me, the states are “carved in stone in opposite”. If I check an unchecked checkbox it becomes checked for ever, regardles of reload. The same is true if I uncheck a checked checkbox.

It works like a charm if I wrap the elements in a div.custom-checkbox on page load. But that’s no good.

Comment by Jens Hedqvist on 03/15  at  03:43 PM

I fixed the horizontal alignment issue.  This simple change will align all the boxes or buttons in a horizontal row.  Just add a float left to the container but do NOT remove the position relative.

/* wrapper divs */
.custom-checkbox, .custom-radio { float:left; position: relative; }

Then adjust all the various margins & paddings to your liking.

Comment by John on 03/18  at  04:24 AM

I had some problems with this plugin using arrays as input names after upgrading the jQuery and jQueryUI to the latest versions (1.5.1 and 1.8.11). Firebug reported the error:
uncaught exception: Syntax error, unrecognized expression: [name=section[1]]

I solved the error by changing
var allInputs = $(’input[name=’+input.attr(’name’)+’]’);
around line 26 to
var allInputs = $("input[name=’"+input.attr(’name’)+"’]");

It worked fine using previous versions, but somehow something in the jquery updates messed things up. I hope this can help somebody else having the same problem…

Comment by Sebastian on 03/23  at  06:45 PM

“I solved the error by changing
var allInputs = $(’input[name=’+input.attr(’name’)+’]’);
around line 26 to
var allInputs = $("input[name=’"+input.attr(’name’)+"’]"); “

I had the same exact issue- solved the same way.  It also happens with 1.6 RC1

Comment by NgM on 04/29  at  11:18 PM

Nice sript but how do you uncheck it in jquery?

Comment by fritz on 05/02  at  05:06 PM

Just removing the “checked” attribute doesn’t change the class so you have use this.

$(’input[name="radiobutton"]).removeAttr("checked").trigger(’updateState’);

It works in Firefox 4 and IE 8.

Comment by fritz on 05/03  at  10:12 AM

the fix for line 26 works great. I’m surprised you don’t update the script linked from this page…

Comment by myq on 05/03  at  01:26 PM

Great - it works well on dev. Will try it on a live site and see how it performs!

Comment by Chris on 06/04  at  10:43 AM

Perhaps someone might be able to assist me. I’ve spent the last four hours just trying to get this to work locally (it works fine on this website) but for the life of me cannot figure out why it won’t locally. I’ve copied and stored all required files (both .js and .css) but every time I go to view the page, it just shows the default checkboxes and radio buttons. Is there an additional file somewhere that I’m missing? Help.

Comment by Cole on 06/15  at  12:09 AM

Ok, I’m an idiot. my CSS URL path was incorrect. Please igrore the previous post. :{

Comment by Cole on 06/15  at  12:43 AM

There was a post sometime ago by Alipix mentioning that these inputs do not work within a dialog box.  I had the same problem and solved it by changing the z-index for the custom labels and inputs.  By default the jquery ui dialog has a z-index set to 1000, which puts the dialog on top of these inputs and prevents the click event from working properly on them.  Setting the z-index of your custom inputs and labels to something like 2000 and 2001 respectively should fix it.

Comment by MrChris on 06/18  at  01:05 AM

The best way to customize checkbox and make it compatible with ie6 :

http://www.dreamsnet.it/2011/06/personalizzare-checkbox-html-customizing-checkbox-form-html-ie6-compatibile/

Simple ZIP demo to download and videotutorial (in italian).

Comment by Marco Marcoaldi on 06/23  at  04:43 PM

Commenting is closed for this post.

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site