Here is how to do this properly using progressive enchancement. The idea is simple, turn a decently marked up HTML form into nicer color and size swatch buttons for users that have JavaScript.
The benefits of this method are many, you can easily switch, or remove, components (JS/CSS) and have the form still work. It will work for users that don't hvae javascript, and if we're careful, it will be accessible via keyboard and screen readers. It also requires no extra code on submit, and will be a lot easier to use with a form validator.
Most of the work is done with CSS with only minimal JavaScript to add the finishing touches.
Full working example example can be found on jsbin. It includes both color and size selections. I have only included the colors here as there is only a slight difference in the CSS between the them.
The HTML
First off, markup the form components. Here are the basics, you may obviously want to add things, like a wrapper around the whole widget, or use other classnames.
<label>Color</label>
<ul class="radio color">
<li class="red">
<input type="radio" name="color" id="color_red" value="red" />
<label for="color_red">Red</label>
</li>
<li class="green">
<input type="radio" name="color" id="color_green" value="green" />
<label for="color_green">Green</label>
</li>
<!-- ...and so on... -->
</ul>
This may seem a lot for "basics", but there are several things going on here. The whole UL
container is flagged as "radio" so everything within can be targeted appropriately with CSS ("ul.radio label"
).
Additionally I add a class corresponding to the input's name, and a class on each LI
echoing the value to allow CSS to add appropriate styles ("ul.color li.red"
). The rest is pretty much your standard form markup.
The JavaScript
The JavaScript is very simple. All we do is detect which radio input is currently selected and flag the container LI
with a "selected" class, thus allowing CSS to highlight it appropriately.
jQuery(function($){
// notify CSS that JS is present
$( 'body' ).addClass( 'js-active' );
// for every radio control...
$( 'ul.radio' )
// redo every change
.bind('click', function () {
// refresh "current classes"
$( 'li', this ).removeClass( 'selected' )
.filter( ':has(:checked)' ).addClass( 'selected' );
})
// trigger initial run
.trigger( 'click' );
});
Please note: This "trick" of triggering click to enforce the initial run may be unsafe if you have click handlers higher (typically document.body
), I use this method for brevity.
The CSS
Here is where the real magic happens. The idea is that we can tuck the input controls out of view and style the labels as color buttons. So long as the for
attributes on the labels correctly point to id
's on the inputs then clicking the labels will suffice to select the control correctly.
We need to target things only within ".js-active"
so that users that don't have JavaScript but still have CSS will see the radio input elements. Some sort of ".radio-js" class could alternatively be added on the UL
wrapper to get the same thing done.
Firstly, float the buttons and give them a more button-like appearance:
/* attributes are lined up horizontally */
ul.color li,
ul.size li {
border : 1px solid #aaa;
margin : 0 4px 0 0;
padding : 2px;
float : left;
overflow : hidden;
position : relative;
}
We put position:relative
and overflow:hidden
on each LI so we can shift the input element out of view. This will keep it accessible, but we just no longer have to look at it:
/* if JS: remove inputs from sight */
.js-active ul.color input,
.js-active ul.size input {
position : absolute;
left : -999em;
}
Next up is turning the labels into buttons:
/* if JS: turn color labels into buttons */
.js-active ul.color label {
cursor : pointer;
display : block;
height : 18px;
width : 18px;
text-indent : -999em; /* hide text */
}
/* color buttons in color -- allowed even if no JS */
ul.color li.red label { background : red; }
ul.color li.green label { background : green; }
ul.color li.blue label { background : blue; }
Finally, set some styles for our highlighted / selected item.
/* if JS: current selected highlight */
.js-active ul.color .selected,
.js-active ul.size .selected {
border : 2px solid #000;
padding : 1px;
}
For completeness: Getting the select values if you want to add more effects is demonstrated elsewhere on SO.