Example Start

Lunch Options

Drink Options

Example End

Example Description

Type: Best Practice

Simple example of a radio button widget using ARIA CSS selectors to display checked state. Background images are typically not displayed in high contrast mode. For this reason, background images should not be relied upon to display essential visual information.

Keyboard Support

  • Tab: Move between button items and text area.
  • Enter or space: Toggle aria-checked state of checkbox with keyboard focus.

Example Markup

Browser Compatibility

HTML Source Code


<div role="application">

  <h3 id="rg1_label">Lunch Options</h3>

  <ul class="radiogroup"
      id="rg1"
      role="radiogroup"
      aria-labelledby="rg1_label"
   >
    <li id="r1"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Thai
    </li>
    <li id="r2"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Subway
    </li>
    <li id="r3"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Jimmy Johns
    </li>
    <li id="r4"
      tabindex="0"
      role="radio"
      aria-checked="true">
      Radio Maria
    </li>
    <li id="r5"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Rainbow Gardens
    </li>  
  </ul>  

<!-- Start of second Radio Group  -->

  <h3 id="rg2_label">Drink Options</h3>
  
  <ul id="rg2"
      class="radiogroup"
      role="radiogroup"
      aria-labelledby="rg2_label"
   >
    <li id="r6"
      tabindex="0"
      role="radio"
      aria-checked="false">
      Water
    </li>
    <li id="r7"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Tea
    </li>
    <li id="r8"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Coffee
    </li>
    <li id="r9"
      tabindex="-1"
      role="radio"
      aria-checked="false">
      Cola
    </li>
    <li id="r10"
      class="last"
      tabindex="0"
      role="radio"
      aria-checked="false">
      Ginger Ale
    </li>  
  </ul>  

</div>

CSS Code


ul.radiogroup {
    margin: 0;
    padding: 0;    
}

ul.radiogroup  li   {
    margin: 0;
    margin-left: 1em;
    padding: 4px;
  padding-left: 20px;
    list-style: none;
    width: 6em;    
}
    
ul.radiogroup li:hover {
  padding: 2px 18px;
  background-position: 2px 4px;
    border: gray 2px solid;
}

ul.radiogroup li.selected {
  padding: 2px 18px;
  background-position: 2px 4px;
    border: black 2px solid;
   }

.offscreen {
  position: absolute;
    left: -200em;
    top: -20em;
}

li[aria-checked='true'] {
      background: url('http://www.oaa-accessibility.org/media/examples/images/radio-checked.gif') no-repeat 4px 6px;
}

li[aria-checked='false'] {
      background: url('http://www.oaa-accessibility.org/media/examples/images/radio-unchecked.gif') no-repeat 4px 6px;
}

Javascript Source Code


$(document).ready(function() {

  var group1 = new radioGroup('rg1');
  var group2 = new radioGroup('rg2');

}); // end ready

function keyCodes () {
  // Define values for keycodes
  this.enter      = 13;
  this.space      = 32;

  this.left       = 37;
  this.up         = 38;
  this.right      = 39;
  this.down       = 40;
}

//
// Function radioGroup() is a class to define an ARIA-enabled radiogroup widget.
//
// This widget attaches to an unordered list and makes each list entry a group
// of radio buttons.
//
// @param (id object) id is the html id of the <ul> to attach to
//
// @return N/A
//
function radioGroup(id) {

  var thisObj = this;

  ///////// define widget properties ///////////////

  this.$id = $('#' + id);

  // find all list items with a role of radio
  this.$buttons = this.$id.find('li').filter('[role=radio]');

  // Store the currently checked item
  this.$checked = this.$buttons.filter('[aria-checked=true]');

  this.checkButton = true; // set to false during ctrl+arrow operations;

  this.$active = null; // the selected button (may not be checked)
  
  this.keys = new keyCodes();

  ///////////// Bind Event handlers ////////////////

  this.$buttons.click(function(e) {
    return thisObj.handleClick(e, $(this));
  });

  this.$buttons.keydown(function(e) {
    return thisObj.handleKeyDown(e, $(this));
  });

  this.$buttons.keypress(function(e) {
    return thisObj.handleKeyPress(e, $(this));
  });

  this.$buttons.focus(function(e) {
    return thisObj.handleFocus(e, $(this));
  });

  this.$buttons.blur(function(e) {
    return thisObj.handleBlur(e, $(this));
  });
}

//
// Function selectButton() is a member function to select and possibly check a button in the
// radioGroup.
//
// @param ($id object) $id is the jQuery object of the button to select
//
// @return N/A
//
radioGroup.prototype.selectButton = function($id) {

  if (this.checkButton == true) {
    // checking the button

      // set the previous button's aria-checked attribute to false
      this.$checked.attr('aria-checked', 'false');

      // set the new button's aria-checked attribute to true
      $id.attr('aria-checked', 'true');

    if (this.$checked.length == 0) { // no previously checked group buttons
      // the first and last items in the group will have
      // tabindex=0. Remove them both from the tab order.
      this.$buttons.first().attr('tabindex', '-1');

      this.$buttons.last().attr('tabindex', '-1');
    }
    else {
      // remove the previously checked item from
      // the tab order
      this.$checked.attr('tabindex', '-1');
    }

    // Place this button in the tab order
    $id.attr('tabindex', '0');

    // update the stored $checked object
    this.$checked = $id;
  }

  // reset the checkButton flag - in case it was false
  this.checkButton = true;

  // update the stored $active object
  this.$active = $id;

  // give this button the selected class
  $id.addClass('selected');

} // end selectButton()

//
// Function handleKeyDown() is a member function to process keydown events for the radioGroup.
//
// @param (e object) e is the event object
//
// @param ($id object) $is is the jquery object of the triggering element
//
// @return (boolean) Returns false if consuming event; true if propagating
//
radioGroup.prototype.handleClick = function(e, $id) {

  if (e.altKey || e.ctrlKey || e.shiftKey) {
    // do nothing
    return true;
  }

  // simply consume the event - browser calls focus

  e.stopPropagation();
  return false;

} // end handleClick()

//
// Function handleKeyDown() is a member function to process keydown events for the radioGroup.
//
// @param (e object) e is the event object
//
// @param ($id object) $is is the jquery object of the triggering element
//
// @return (boolean) Returns false if consuming event; true if propagating
//
radioGroup.prototype.handleKeyDown = function(e, $id) {

  if (e.altKey) {
    // do nothing
    return true;
  }

  switch (e.keyCode) {
    case this.keys.space:
    case this.keys.enter: {
      if (e.ctrlkey || e.shiftKey) {
        // do nothing
        return true;
      }

      // select and check the button
      this.selectButton($id);

      e.stopPropagation();
      return false;
    }
    case this.keys.left:
    case this.keys.up: {

      var $prev = $id.prev(); // the previous button

      if (e.shiftKey) {
        // do nothing
        return true;
      }

      // if this was the first item
      // select the last one in the group.
      if ($id.index() == 0) {
        $prev = this.$buttons.last();
      }

      if (e.ctrlKey) {
        // set checkButton to false so
        // focus does not check button
        this.checkButton = false;
      }

      // give the previous button focus
      $prev[0].focus();

      e.stopPropagation();
      return false;
    }
    case this.keys.right:
    case this.keys.down: {

      var $next = $id.next(); // the next button

      if (e.shiftKey) {
        // do nothing
        return true;
      }

      // if this was the last item,
      // select the first one in the group.
      if ($id.index() == this.$buttons.length - 1) {
        $next = this.$buttons.first();
      }

      if (e.ctrlKey) {
        // set checkButton to false so
        // focus does not check button
        this.checkButton = false;
      }

      // give the next button focus
      $next[0].focus();

      e.stopPropagation();
      return false;
    }
  } // end switch

  return true;

} // end handleKeyDown()

//
// Function handleKeyPress() is a member function to process keydown events for the radioGroup.
// This is needed to prevent browsers that process window events on keypress (such as Opera) from
// performing unwanted scrolling of the window, etc.
//
// @param (e object) e is the event object
//
// @param ($id object) $is is the jquery object of the triggering element
//
// @return (boolean) Returns false if consuming event; true if propagating
//
radioGroup.prototype.handleKeyPress = function(e, $id) {

  if (e.altKey) {
    // do nothing
    return true;
  }

  switch (e.keyCode) {
    case this.keys.space:
    case this.keys.enter: {
      if (e.ctrlKey || e.shiftKey) {
        // do nothing
        return true;
      }
    }
    case this.keys.left:
    case this.keys.up:
    case this.keys.right:
    case this.keys.down: {
      if (e.shiftKey) {
        // do nothing
        return true;
      }
      e.stopPropagation();
      return false;
    }
  } // end switch

  return true;

} // end handleKeyPress()

//
// Function handleFocus() is a member function to process focus events for the radioGroup.
//
// @param (e object) e is the event object
//
// @param ($id object) $is is the jquery object of the triggering element
//
// @return (boolean) Returns false if consuming event; true if propagating
//
radioGroup.prototype.handleFocus = function(e, $id) {

  // Do button selection processing
  this.selectButton($id);

  return true;
} // end handleFocus()

//
// Function handleBlur() is a member function to process blur events for the radioGroup.
//
// @param (e object) e is the event object
//
// @param ($id object) $is is the jquery object of the triggering element
//
// @return (boolean) Returns false if consuming event; true if propagating
//
radioGroup.prototype.handleBlur = function(e, $id) {

  // remove the focus styling from this buttons
  $id.removeClass('selected');

  return true;
} // end handleBlur()