Skip to content

February 9, 2014

Simple, Light-Weight Radio Button Replacement jQuery Plugin

I was looking for a jQuery plugin to replace the standard browser radio buttons with custom images, but I had a few requirements. 1) Tabbing and focus had to function correctly, and 2) the javascript had to be light-weight. After looking into some heavy plugins and some simple ones that didn’t handle tabbing/focus properly, I decided to roll my own.

Here’s the plugin code, with comments:

(function($) {
    // source: http://www.gotoquiz.com/web-coding/
    'use strict';
    
    //the plugin
    $.fn.niceRadio = function(options) {
        if (this.length !== 0) {
            //initialize settings & vars
            var settings = $.extend(
                {   baseClass:      'nr_base',
                    focusClass:     'nr_focus',
                    checkedClass:   'nr_checked'},
                options), 
                $radios = this,
                $aset = $();
            
            $radios.each(function(i, el) {
                var $radio = $(el),
                    //create an anchor per radio that will be our radio replacement
                    $a = $('<a href="#" id="nr_a'+ i +'" data-nr-id="'+ i +'" class="'+ 
                            settings.baseClass +' nr_for_'+ $radio.attr('name') +
                            '" tabindex="-1"></a>');

                //give each radio a custom class with a counter (nr_r#) for easy lookup
                $radio.addClass('nr_r'+ i)
                    //store the counter value as data as well
                    .data('nrId', i)
                    //insert the anchor immediately after the radio
                    .after($a)
                    //hide the radio visually
                    .css({position: 'absolute', 'margin-left': -2999});
            
                //if radio was already checked, add the checked class to it's replacement
                if ($radio.is(':checked'))
                    $a.addClass(settings.checkedClass);

                //collect the set of anchors as they are created
                $aset = $aset.add($a);
            });


            //when an anchor is clicked...
            $aset.click(function(e) {
                e.preventDefault();

                //$clicked is the anchor
                var $clicked = $(this),
                    //$checked is the radio to be checked
                    $checked = $('.nr_r'+ $clicked.data('nrId'));

                //check it
                $checked.prop('checked', true)
                    //focus it
                    .focus()
                    //trigger the change event on it
                    .change();
            });


            //when a radio received focus, add the focus class to its replacement
            $radios.focus(function() {
                $('#nr_a'+ $(this).data('nrId')).addClass(settings.focusClass);
            })
            //when a radio loses focus, remove the focus class from it's replacement
            .blur(function() {
                $('#nr_a'+ $(this).data('nrId')).removeClass(settings.focusClass);
            })
            //when a radio is changed, call the changeEvent function
            .change(function() {
                changeEvent($(this));
            });


            return $radios;
        }

        //called when a radio selection changes
        function changeEvent($radio) {
            //if $radio is checked...
            if ($radio.prop('checked')) {
                //remove checked class from any other anchor associated with
                //radios of the same name
                $('.nr_for_'+ $radio.attr('name')).removeClass(settings.checkedClass);
                //add checked class to this radio's replacement
                $('#nr_a'+ $radio.data('nrId')).addClass(settings.checkedClass);
            }
            else
                $('#nr_a'+ $radio.data('nrId')).removeClass(settings.checkedClass);
        }
    };
    
})(jQuery);

This requires some CSS rules, which might look like:

.nr_base		     { display: inline-block; 
                       height: 16px; 
                       width: 16px; 
                       background: transparent url(radio_sprite.png) no-repeat 0 0;
                       vertical-align: middle; }
.nr_checked	         { background-position: -16px 0; }
.nr_focus		     { outline: 1px dotted #555; }
.nr_checked.nr_focus { outline: none; }

Minified, this radio replacement plugin shrinks to under 1KB. It handles tabbing and focus properly, for both screen readers and keyboard users. It handles radio changes via clicking associated labels. And by using an anchor tag to hold the radio replacement image, it handles the odd browser that does not treat labels as clickable (a problem with some of the other radio replacement plugins I encountered).

Feel free to use this plugin as you like, and I hope it proves useful! Let me know if you find any bugs or particular browser quirks.

Read more from Javascript

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments