Radio and Radio Group

Radio and Radio Group

Overview

It is always best to provide native HTML radio buttons, rather than custom radio buttons, but when coded correctly, the custom buttons can and should act like native HTML radio buttons.

See also the Official W3C documentation about ARIA radio buttons and radio groups.



Turn on a screen reader to experience this example in action.

What's your favorite framework?
React Angular Ember None
Which one of these scientists most inspires you?
Marie Curie Jane Goodall Rosalind Franklin Hedy Lamarr

Initial HTML Markup

All radio groups require a group label of some kind. The classic method is using <fieldset> and <legend>. The ARIA method of role="radiogroup" and aria-labelledby can also be used.

Method 1: <fieldset> and <legend>

<fieldset class="deque-radio-group">
<legend class="deque-radio-group-label" >What's your favorite framework?</legend>
  <div class="radioGroup">
    <span id="Whatsyourfavoriteframework_0" class="deque-radio" aria-labelledby="react"></span>
    <span id="react" >React</span>

    <span id="Whatsyourfavoriteframework_1" class="deque-radio" aria-labelledby="angular"></span>
    <span id="angular" >Angular</span>

    <span id="Whatsyourfavoriteframework_2" class="deque-radio" aria-labelledby="ember"></span>
    <span id="ember" >Ember</span>

    <span id="Whatsyourfavoriteframework_3" class="deque-radio" aria-labelledby="none"></span>
    <span id="none" >None</span>
  </div>
</fieldset>

Method 2: role="radiogroup" and aria-labelledby

<div class="deque-radio-group"  aria-labelledby="inspire">
  <div class="deque-radio-group-label" id="inspire" >
    Which one of these scientists most inspires you?</div>
  <div class="radioGroup">
    <span id="Whichoneofthesescientistsmostinspiresyou_0" class="deque-radio" aria-labelledby="curie"></span>
    <span id="curie" >Marie Curie</span>

    <span id="Whichoneofthesescientistsmostinspiresyou_1" class="deque-radio" aria-labelledby="goodall"></span>
    <span id="goodall" >Jane Goodall</span>

    <span id="Whichoneofthesescientistsmostinspiresyou_2" class="deque-radio" aria-labelledby="franklin"></span>
    <span id="franklin" >Rosalind Franklin</span>

    <span id="Whichoneofthesescientistsmostinspiresyou_3" class="deque-radio" aria-labelledby="lamarr"></span>
    <span id="lamarr" >Hedy Lamarr</span>
  </div>
</div>

JavaScript

Required: The complete JavaScript file (for all patterns in the library): deque-patterns.min.js

Key parts of the JavaScript file

Note: The code below is functional only in the context of the complete JavaScript file.

In the radio section:


      function createRadioGroup(group, label, buttons, onChange) {
        var radioGroup = group.querySelector('.radioGroup');
        radioGroup.setAttribute('role', 'radiogroup');

        var currentFocus = 0;

        buttons = Array.prototype.slice.call(buttons); //convert from a HTMLCollection to a javascript array to be able to do the .map function
        buttons = buttons.map(function (button) {
          var labelText = button.getAttribute('aria-labelledby');
          var label = document.getElementById(labelText);

          setUniqueToggle(button);
          return (0, _checkbox.createSingleCheckboxForRadio)(button, label, false, function (e) {
            // note that we are ignoring e.isToggledOn - we are using checkboxes
            // to implement radio buttons, so we don't toggle it on click. We manually
            // override the toggle state by calling toggleOn() or toggleOff() explicitly.
            setUniqueToggle(e.element);

            if (onChange) {
              onChange(button);
            }
          });
        });

        buttons.forEach(function (button) {
          button.addEventListener('focus', handleFocus);
          button.addEventListener('keydown', handleKeydown);
          button.setAttribute('role', 'radio');
          //button.setAttribute('tabindex', '-1');
          var indicator = button.querySelector('.deque-checkbox-indicator');
          indicator.classList.remove('deque-checkbox-indicator');
          indicator.classList.add('deque-radio-indicator');

          var labelText = button.getAttribute('aria-labelledby');
          var label = document.getElementById(labelText);
          label.classList.remove('deque-checkbox-label');
          label.classList.add('deque-radio-label');
        });

        buttons[0].setAttribute('tabindex', '0');

        function handleFocus(e) {
          var currentParent = e.currentTarget.parentNode.parentNode;
          var ua = window.navigator.userAgent;
          var msie = ua.indexOf('Trident/');
          var msedge = ua.indexOf('Edge');
          if (msie > 0 || msedge > 0) {
            if (currentParent.nodeName.toLowerCase() == 'FIELDSET'.toLowerCase()) {
              if (!currentParent.querySelector('legend').getAttribute('id')) {
                currentParent.querySelector('legend').setAttribute('id', 'deque-fieldset-radio-group');
              }
              if (!e.currentTarget.getAttribute('aria-describedby')) {
                e.currentTarget.setAttribute('aria-describedby', currentParent.querySelector('legend').getAttribute('id'));
              }
            }
          }
          var index = e.target.id.split('_')[1];
          currentFocus = parseInt(index);
          buttons[currentFocus].click();
        }

        function handleKeydown(e) {
          var key = e.which;
          if (key === _keyboardUtils.KEYS.RIGHT || key === _keyboardUtils.KEYS.DOWN) {
            focusNext(e);
          } else if (key === _keyboardUtils.KEYS.LEFT || key === _keyboardUtils.KEYS.UP) {
            focusPrev(e);
          }

          if (key == _keyboardUtils.KEYS.TAB) {
            var parentElement = null;
            parentElement = buttons[currentFocus].parentNode.parentNode;
            if (e.shiftKey) {
              try {
                if (parentElement.previousSibling.previousSibling.querySelectorAll('.deque-radio')[0]) {
                  if (parentElement.previousSibling.previousSibling.querySelector('.deque-radio[aria-checked="true"]')) {
                    parentElement.previousSibling.previousSibling.querySelector('.deque-radio[aria-checked="true"]').setAttribute('tabindex', '0');
                    parentElement.previousSibling.previousSibling.querySelector('.deque-radio[aria-checked="true"]').focus();
                  } else {
                    parentElement.previousSibling.previousSibling.querySelectorAll('.deque-radio')[0].setAttribute('tabindex', '0');
                    parentElement.previousSibling.previousSibling.querySelectorAll('.deque-radio')[0].focus();
                  }
                } else {
                  parentElement.previousSibling.previousSibling.setAttribute('tabindex', '0');
                  parentElement.previousSibling.previousSibling.focus();
                }
              } catch (e) {
                //error log
              }
            } else {
              try {
                if (parentElement.nextSibling.nextSibling.querySelectorAll('.deque-radio')[0]) {
                  e.currentTarget.setAttribute('tabindex', '0');
                  if (parentElement.nextSibling.nextSibling.querySelector('.deque-radio[aria-checked="true"]')) {
                    parentElement.nextSibling.nextSibling.querySelector('.deque-radio[aria-checked="true"]').setAttribute('tabindex', '0');
                    parentElement.nextSibling.nextSibling.querySelector('.deque-radio[aria-checked="true"]').focus();
                  } else {
                    parentElement.nextSibling.nextSibling.querySelectorAll('.deque-radio')[0].setAttribute('tabindex', '0');
                    parentElement.nextSibling.nextSibling.querySelectorAll('.deque-radio')[0].focus();
                  }
                } else {
                  parentElement.nextSibling.nextSibling.setAttribute('tabindex', '0');
                  parentElement.nextSibling.nextSibling.focus();
                }
              } catch (e) {
                //error log
              }
            }
          }
        }

        function setUniqueToggle(button) {
          buttons.forEach(function (b) {
            b === button ? (0, _checkbox.toggleOn)(b) : (0, _checkbox.toggleOff)(b);
            b.setAttribute('tabindex', b === button ? '0' : '-1');
          });
        }

        function focusPrev(e) {
          if (buttons[currentFocus - 1]) {
            buttons[--currentFocus].focus();
          }
          buttons[currentFocus].click();

          e.preventDefault();
        }

        function focusNext(e) {
          if (buttons[currentFocus + 1]) {
            buttons[++currentFocus].focus();
          }
          buttons[currentFocus].click();

          e.preventDefault();
        }
      }

      function activateAllRadios() {
        var radios = document.querySelectorAll('.deque-radio-group');
        for (var x = 0; x < radios.length; x++) {
          var label = radios[x].querySelector('.deque-radio-group-label');
          var buttons = radios[x].querySelectorAll('.deque-radio');
          if (!radios[x].querySelector('.radioGroup').hasAttribute('role')) {
            createRadioGroup(radios[x], label, buttons);
          }
        }
      }
      activateAllRadios();
      

In the @containerUtils section:


      function elementIsChildOfElement(child, potentialParent) {
        while (child) {
          if (child === potentialParent) {
            return true;
          }

          child = child.parentNode;
        }

        return false;
      }

      function createFieldset(label) {
        var fieldset = document.createElement('fieldset');
        var legend = document.createElement('legend');
        legend.classList.add('legend'); // for easy lookup regardless of mode
        legend.id = (0, _guidUtils.generateGuid)();
        legend.innerText = label;
        fieldset.appendChild(legend);
        return fieldset;
      }

      function createLiveRegion() {
        var level = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'polite';
        var classes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];

        var output = document.createElement('span');
        classes.forEach(function (c) {
          return output.classList.add(c);
        });
        output.id = (0, _guidUtils.generateGuid)();
        output.setAttribute('aria-live', level);
        output.classList.add('deque-visuallyhidden');
        output.innerText = '';
        output.notify = function (text) {
          // TODO: Clean this up...no need to extend the element prototype
          while (output.firstChild) {
            output.removeChild(output.firstChild);
          }
          var msg = document.createElement('div');
          msg.innerHTML = text;
          output.appendChild(msg);
        };

        return output;
      }
      

In the @guidUtils section:


      /*
        note - not a true guid. I prepend 'g' because 
        the ID of an element cannot start with a numeral
      */

      function generateGuid() {
        var S4 = function S4() {
          return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
        };
        return 'g' + (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4());
      }
      

In the @keyboardUtils section:


      var KEYS = exports.KEYS = {
        BACKSPACE: 8,
        TAB: 9,
        ENTER: 13,
        SHIFT: 16,
        CTRL: 17,
        ALT: 18,
        ESCAPE: 27,
        SPACE: 32,
        LEFT: 37,
        RIGHT: 39,
        UP: 38,
        DOWN: 40,
        F10: 121,
        HOME: 36,
        END: 35,
        PAGE_UP: 33,
        PAGE_DOWN: 34
      };

      function bindElementToEventValue(element, eventName, testValue, handler) {
        function localHandler(e) {
          if (e.which === testValue) {
            handler(e);
          }
        }

        element.addEventListener(eventName, localHandler);
        return function () {
          element.removeEventListener(eventName, localHandler);
        };
      }

      function bindElementToKeypressValue(element, testValue, handler) {
        return bindElementToEventValue(element, 'keypress', testValue, handler);
      }

      function bindElementToKeydownValue(element, testValue, handler) {
        return bindElementToEventValue(element, 'keydown', testValue, handler);
      }

      function onElementEnter(element, handler) {
        return bindElementToKeydownValue(element, KEYS.ENTER, handler);
      }

      function onElementEscape(element, handler) {
        return bindElementToKeydownValue(element, KEYS.ESCAPE, handler);
      }

      function onElementSpace(element, handler) {
        return bindElementToKeypressValue(element, KEYS.SPACE, handler);
      }

      function onElementLeft(element, handler) {
        return bindElementToKeydownValue(element, KEYS.LEFT, handler);
      }

      function onElementRight(element, handler) {
        return bindElementToKeydownValue(element, KEYS.RIGHT, handler);
      }

      function onElementUp(element, handler) {
        return bindElementToKeydownValue(element, KEYS.UP, handler);
      }

      function onElementDown(element, handler) {
        return bindElementToKeydownValue(element, KEYS.DOWN, handler);
      }

      function onElementHome(element, handler) {
        return bindElementToKeydownValue(element, KEYS.HOME, handler);
      }

      function onElementEnd(element, handler) {
        return bindElementToKeydownValue(element, KEYS.END, handler);
      }

      function onElementPageUp(element, handler) {
        return bindElementToKeydownValue(element, KEYS.PAGE_UP, handler);
      }

      function onElementPageDown(element, handler) {
        return bindElementToKeydownValue(element, KEYS.PAGE_DOWN, handler);
      }

      function onElementF10(element, handler) {
        return bindElementToKeydownValue(element, KEYS.F10, handler);
      }

      function isAlphaNumeric(charCode) {
        return charCode >= 48 && charCode <= 90 /* numbers, uppercase letters */
          || charCode >= 97 && charCode <= 122 /* lowercase letters */;
      }

      function onElementCharacter(element, handler) {
        function localHandler(e) {
          var charCode = e.which;
          if (isAlphaNumeric(charCode)) {
            handler(e);
          }
        }

        element.addEventListener('keypress', localHandler);
        return function () {
          element.removeEventListener('keypress', localHandler);
        };
      }

      function trapEnter(element) {
        onElementEnter(element, function (e) {
          e.stopPropagation();
          e.preventDefault();
        });
      }
      

In the @checkbox section:


      /*
    	
      TO DO:
    	
      - Throw an error if the label is missing
      */

      function toggle(element) {
        if (isToggledOn(element)) {
          toggleOff(element);
        } else {
          toggleOn(element);
        }
      }

      function isToggledOn(element) {
        return getCheckboxData(element) === 'true';
      }

      function replaceSpace(str) {
        return str.replace(/ /g, '_').toLowerCase();
      }

      function buildCheckboxTristate() {
        var _customCheckboxTristateWidgets = document.querySelectorAll('.custom-checkbox-widget');
        var _instanceChkTristateCount = 0;
        if (_customCheckboxTristateWidgets.length > 0) {
          [].slice.call(_customCheckboxTristateWidgets).forEach(function (_eachCustomWidget) {
            var _dataConfig = {
              groupTitle: _eachCustomWidget.getAttribute('data-group-title'),
              groupOptionTitle: _eachCustomWidget.getAttribute('data-group-option-title'),
              options: _eachCustomWidget.getAttribute('data-options'),
              delimiter: _eachCustomWidget.getAttribute('data-delimiter') || ','
            };

            if (_dataConfig.options) {
              _dataConfig.options = _dataConfig.options.split(_dataConfig.delimiter);
            }

            var _id = 'instance_' + _instanceChkTristateCount + '_' + replaceSpace(_dataConfig.groupTitle);

            var _elementControl = '<div class="custom-checkbox" id="' + _id + '" role="group" aria-labelledby="group-header' + _id + '">';
            _elementControl += '<div class="checkbox-group-heading" id="group-header' + _id + '">' + _dataConfig.groupTitle + '</div>';
            _elementControl += '<div role="checkbox" name="parent-checkbox[]" data-childs="' + _id + '_childs" class="parent-checkbox checkbox-holder" \
	                                              aria-labelledby="' + _id + replaceSpace(_dataConfig.groupOptionTitle) + '" tabindex="0"> \
	                                              <span class="checkbox-indicator"></span> \
	                                              <span class="checkbox-label" id="' + _id + replaceSpace(_dataConfig.groupOptionTitle) + '">' + _dataConfig.groupOptionTitle + '</span> \
	                                          </div> \
	                                          <div class="child-checkbox-list" data-group="' + _id + '">';

            for (var _elementOptionIndex in _dataConfig.options) {
              _elementControl += '<div role="checkbox" name="child-checkbox[]" class="child-checkbox checkbox-holder ' + _id + '_childs" aria-labelledby="' + _id + replaceSpace(_dataConfig.options[_elementOptionIndex]) + '" tabindex="0"> \
	                                                  <span class="checkbox-indicator"></span> \
	                                                  <span class="checkbox-label" id="' + _id + replaceSpace(_dataConfig.options[_elementOptionIndex]) + '">' + _dataConfig.options[_elementOptionIndex] + '</span> \
	                                              </div>';
            }

            _elementControl += '</div></div>';

            _eachCustomWidget.innerHTML = _elementControl;
            _instanceChkTristateCount++;
          });

          var parentCheckboxElements = document.querySelectorAll('.parent-checkbox');
          [].slice.call(parentCheckboxElements).forEach(function (eachParent) {
            eachParent.addEventListener('click', checkboxEvent);
            eachParent.addEventListener('keyup', checkboxEvent);
          });
          var childElements = document.querySelectorAll('.child-checkbox');
          [].slice.call(childElements).forEach(function (eachChildElement) {
            eachChildElement.addEventListener('click', checkboxEvent);
            eachChildElement.addEventListener('keyup', checkboxEvent);
          });
        }
      }

      buildCheckboxTristate();

      function checkboxEvent(event) {
        if (event.keyCode == 13 || event.keyCode == 32 || !event.keyCode) {
          var element = event.currentTarget;
          if (element.getAttribute('aria-checked') == 'true') {
            element.setAttribute('aria-checked', 'false');
            element.classList.remove('active');
          } else {
            element.setAttribute('aria-checked', 'true');
            element.classList.add('active');
          }

          if (element.getAttribute('data-childs')) {
            var childElements = document.querySelectorAll('.' + element.getAttribute('data-childs'));
            [].slice.call(childElements).forEach(function (eachChildElement) {
              if (element.classList.contains('parent-checkbox')) {
                eachChildElement.setAttribute('aria-checked', element.getAttribute('aria-checked'));
              }
            });
          }

          var parentElement = document.querySelector('#' + element.parentElement.getAttribute('data-group'));
          if (parentElement) {
            var innerParent = parentElement.querySelector('.parent-checkbox');
            if (innerParent) {
              var checkboxCounter = parentElement.querySelectorAll('.child-checkbox[aria-checked="true"]').length;
              var _childElements = parentElement.querySelectorAll('.child-checkbox');
              if (checkboxCounter == 0) {
                innerParent.setAttribute('aria-checked', 'false');
              } else if (checkboxCounter < _childElements.length) {
                innerParent.setAttribute('aria-checked', 'mixed');
              } else if (checkboxCounter == _childElements.length) {
                innerParent.setAttribute('aria-checked', 'true');
              }
            }
          }
        }
      }

      function setCheckboxData(element, value) {
        element.setAttribute('aria-checked', value);
        var dataElement = document.getElementById('checkboxTristateData[' + element.getAttribute('aria-labelledby') + ']');
        if (dataElement) dataElement.value = value;
      }

      function getCheckboxData(element) {
        var dataElement = document.getElementById('checkboxTristateData[' + element.getAttribute('aria-labelledby') + ']');
        return dataElement ? dataElement.value : null;
      }

      function toggleOn(element) {
        setCheckboxData(element, 'true');
      }

      function toggleOff(element) {
        setCheckboxData(element, 'false');
      }

      function toggleMixed(element) {
        setCheckboxData(element, 'mixed');
      }
      function createSingleCheckbox(checkbox, isChecked) {
        var onChange = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { };

        checkbox.setAttribute('tabindex', '0');
        checkbox.setAttribute('role', 'checkbox');
        var indicator = document.createElement('span');
        indicator.classList.add('deque-checkbox-indicator');

        checkbox.appendChild(indicator);

        var labelText = checkbox.getAttribute('aria-labelledby');
        var label = document.getElementById(labelText);
        //label.setAttribute('aria-hidden', 'true'); // prevents double readout
        label.classList.add('deque-checkbox-label');

        var hiddenCheckbox = document.createElement('input');
        hiddenCheckbox.type = 'hidden';
        hiddenCheckbox.name = 'checkboxTristateData[' + labelText + ']';
        hiddenCheckbox.id = 'checkboxTristateData[' + labelText + ']';
        hiddenCheckbox.classList.add('deque-checkbox-data');

        checkbox.appendChild(hiddenCheckbox);

        /*checkbox.addEventListener('focus', function () {
          var allCheckboxElements = document.querySelectorAll('.deque-checkbox-tristate-parent');
          [].slice.call(allCheckboxElements).forEach(element => {
            element.setAttribute('aria-hidden', 'true');
          });
        });*/

        if (isChecked) {
          toggleOn(checkbox);
        } else {
          toggleOff(checkbox);
        }

        function changeHandler(e) {
          e.stopPropagation();
          e.preventDefault();
          toggle(checkbox);
          broadcastChange();
        }

        function broadcastChange() {
          onChange({ element: checkbox, isToggledOn: isToggledOn(label) });
        }

        checkbox.parentNode.addEventListener('click', changeHandler);
        (0, _keyboardUtils.onElementSpace)(checkbox, changeHandler);
        (0, _keyboardUtils.onElementEnter)(checkbox, changeHandler);

        checkbox.parentNode.addEventListener('focus', function () {
          checkbox.classList.add('deque-checkbox-focused');
        });

        checkbox.parentNode.addEventListener('blur', function () {
          checkbox.classList.remove('deque-checkbox-focused');
        });

        return checkbox;
      }

      function createSingleCheckboxForRadio(checkbox, checkboxLabel, isChecked) {
        var onChange = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () { };


        checkbox.setAttribute('tabindex', '0');
        checkbox.setAttribute('role', 'checkbox');

        var indicator = document.createElement('span');
        indicator.classList.add('deque-checkbox-indicator');

        checkbox.appendChild(indicator);

        var labelText = checkbox.getAttribute('aria-labelledby');
        var label = document.getElementById(labelText);
        //label.setAttribute('aria-hidden', 'true'); // prevents double readout
        label.classList.add('deque-checkbox-label');

        var hiddenRadio = document.createElement('input');
        hiddenRadio.type = 'hidden';
        hiddenRadio.name = 'checkboxTristateData[' + labelText + ']';
        hiddenRadio.id = 'checkboxTristateData[' + labelText + ']';
        hiddenRadio.classList.add('deque-checkbox-radio-data');
        checkbox.appendChild(hiddenRadio);
        checkbox.appendChild(label);

        if (isChecked) {
          toggleOn(checkbox);
        } else {
          toggleOff(checkbox);
        }

        function changeHandler(e) {
          e.stopPropagation();
          e.preventDefault();
          toggle(checkbox);
          broadcastChange();
        }

        function broadcastChange() {
          onChange({ element: checkbox, isToggledOn: isToggledOn(label) });
        }

        checkbox.addEventListener('click', changeHandler);
        checkbox.addEventListener('keydown', changeHandler);

        checkboxLabel.addEventListener('click', changeHandler);
        (0, _keyboardUtils.onElementSpace)(checkbox, changeHandler);

        checkbox.addEventListener('focus', function () {
          checkbox.classList.add('deque-radio-focused');
        });

        checkbox.addEventListener('blur', function () {
          checkbox.classList.remove('deque-radio-focused');
        });

        return checkbox;
      }

      function createCheckboxGroup(parent, children) {
        var onChange = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { };


        parent = createSingleCheckbox(parent, false, function (e) {
          onChange(e);
          rootClicked(getCorrectRootState());
        });

        children = Array.prototype.slice.call(children);

        children = children.map(function (child) {
          return createSingleCheckbox(child, false, function () {
            if (onChange) {
              onChange(child);
            }
            setCorrectRootState();
          });
        });

        var rootClickHandlers = {
          'true': function _true() {
            children.forEach(toggleOff);
            toggleOff(parent);
          },
          'false': function _false() {
            children.forEach(toggleOn);
            toggleOn(parent);
          },
          'mixed': function mixed() {
            children.forEach(toggleOn);
            toggleOn(parent);
          }
        };

        function rootClicked(rootState) {
          rootClickHandlers[rootState]();
        }

        function getCorrectRootState() {
          if (children.every(isToggledOn)) {
            return 'true';
          } else if (children.every(function (child) {
            return !isToggledOn(child);
          })) {
            return 'false';
          } else {
            return 'mixed';
          }
        }

        var leafClickHandlers = {
          'true': function _true() {
            return toggleOn(parent);
          },
          'false': function _false() {
            return toggleOff(parent);
          },
          'mixed': function mixed() {
            return toggleMixed(parent);
          }
        };

        function setCorrectRootState() {
          leafClickHandlers[getCorrectRootState()]();
        }
      }

      function activateAllCheckboxes() {
        var checkboxes = document.querySelectorAll('.deque-checkbox-aria');
        for (var i = 0; i < checkboxes.length; i++) {
          var childNode = checkboxes[i].querySelector('.deque-checkbox-data');
          if (!checkboxes[i].contains(childNode)) {
            createSingleCheckbox(checkboxes[i], false);
          }
        }

        var tristates = document.querySelectorAll('.deque-checkbox-tristate-group');
        for (var j = 0; j < tristates.length; j++) {
          var parentGroup = tristates[j].querySelector('.deque-checkbox-tristate-parent');
          var parent = parentGroup.querySelector('.deque-checkbox-tristate');
          var childrenGroup = tristates[j].querySelector('.deque-checkbox-tristate-children');
          var children = childrenGroup.querySelectorAll('.deque-checkbox-tristate');
          childNode = childrenGroup.querySelector('.deque-checkbox-data');
          if (!childrenGroup.contains(childNode)) {
            createCheckboxGroup(parent, children);
          }
        }
      }

      activateAllCheckboxes();

      

Note: No additional JavaScript initialization code is necessary for this pattern. All elements with class="deque-radio-group" will be initialized automatically by the external JavaScript file.

CSS

Required: The complete CSS file (for all patterns in the library): deque-patterns.min.css

Key styles within the CSS file (other styles may also be necessary):


.deque-radio-group {
  margin: 0;
  padding: 10px;
}
.deque-radio-group legend,
.deque-radio-group .deque-radio-group-label {
  font-size: 16px;
  display: block;
  margin-top: 21px;
  padding-bottom: 7px;
  line-height: 20px;
}
.deque-radio-label {
  font-size: 15px;
  margin-right: 24px;
  padding-bottom: 0;
  position: relative;
  cursor: default;
  display: inline-block;
  margin-left: 28px;
  line-height: 1.75;
}
.deque-radio[role='radio'] {
  cursor: default;
  user-select: none;
  font-size: 15px;
  line-height: 20px;
  margin-right: 24px;
  padding-bottom: 0;
  position: relative;
  display: block;
  margin-top: 2px;
}
.deque-radio[role='radio']:hover {
  opacity: 0.75;
}
.deque-radio[role='radio'] .deque-radio-focused {
  outline: 1px dashed #000000;
}
.deque-radio[role='radio'] .deque-radio-indicator {
  border: solid #000000 1px;
  border-radius: 50%;
  height: 20px;
  width: 20px;
  position: absolute;
}
.deque-radio[role='radio'][aria-checked='true'] .deque-radio-indicator {
  border-color: #006cc1;
}
.deque-radio[role='radio'][aria-checked='true'] .deque-radio-indicator:before {
  background: currentColor;
  border-radius: 50%;
  height: 10px;
  width: 10px;
  border: 1px solid #ffffff;
  content: ' ';
  left: 4px;
  position: absolute;
  top: 4px;
}
.deque-radio[role='radio'][aria-checked='mixed'] .deque-radio-indicator {
  background: #ffffff;
}
.deque-radio[role='radio'] ul[role='radiogroup'] {
  list-style-type: none;
}

Implementation Instructions

Step 1: Add Dependencies

Add deque-patterns.min.css in the <head> of the document.

<link rel="stylesheet" type="text/css" href="deque-patterns.min.css">

Add a script link to deque-patterns.min.js to the bottom of the page.

<script type="text/javascript" src="deque-patterns.min.js"></script>

Step 2: Add HTML

  • There are two methods to implement radio buttons.
    • Method 1: This creates a box around the group
      • Create a <fieldset> container with class="deque-radio-group".
      • Add a <legend> container with class="deque-radio-group-label". The inner text is the question displayed at the top of the group of radio buttons.
        • All of the radio button should be placed within a <div> container.
          • Create a <div> or <span> container. Add the class="deque-radio" for styling purposes and so the javascript can initialize each radio button automatically. Also add the attribute aria-labelledby attribute that corresponds with the radio's label ID and a unique ID that ends with an underscore and the index of that specific answer (e.g. Whatisyourfavoriteframework_0). This container creates the box that becomes marked when selected.
          • Add a container with a unique ID. The inner text will be the content of the radio button.
    • Method 2:
      • Create a <div> or <span> container with class="deque-radio-group", role="radiogroup", and the attribute aria-labelledby attribute that corresponds with the radio's group label ID.
      • Add a <div> or <span> container with class="deque-radio-group-label" and a unique ID. The inner text is the question displayed at the top of the group of radio buttons.
        • All of the radio button should be placed within a <div> container.
          • Create a <div> or <span> container. Add the class="deque-radio" for styling purposes and so the javascript can initialize each radio button automatically. Also add the attribute aria-labelledby attribute that corresponds with the radio's label ID and a unique ID that ends with an underscore and the index of that specific answer (e.g. Whatisyourfavoriteframework_0). This container creates the box that becomes marked when selected.
          • Add a container with a unique ID. The inner text will be the content of the radio button.