3

UPDATE The following problem occurs even after trying out the suggestions here. The latest code snippet demonstrates all 3 approaches of hiding a Radio Button and breaking / (Up/Down arrow keys) keyboard navigation in the radio group in Firefox & IE.


Suppose I have a Radio Group, each radio button with its label in a DIV. I use the arrow keys (up/down) to browse my radio buttons once at least one of them has focus.

One of the radio buttons in my Radio Group is hidden. It's in a DIV which has display:none; (but I also tried visibility:hidden and position:fixed;opacity:0 as possible alternatives).

I noticed that in Chrome, I can still use the up/down arrows to traverse the focused list without problems, but in Firefox and IE my navigation breaks when the focus is supposed to shift to the radiobutton over the hidden one.

To see this, do the following in this snippet:

1) In Firefox or IE vs. Chrome, first select Radio Button #1 with the mouse (in each column, to see each approach)

2) Now use the key to navigate to the end of the list: see that it breaks in Firefox and IE, but works in Chrome. The group is deselected and you lose focus in Firefox and IE.

3) You can also try it from the end in reverse order, it'll break the same way.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>

<table>
<tr>
<td>
<span style="font-weight:bold;">With display:none</span>
</td>
<td>
<span style="font-weight:bold;">With visibility:hidden</span>
</td>
<td>
<span style="font-weight:bold;">With position:fixed;opacity:0;</span>
</td>

</tr>
<tr>
<td>

<div>
   <input id="opt1a" type="radio" name="group" value="option1">
   <label for="opt1a">Option 1</label>
</div>
<div>
   <input id="opt2a" type="radio" name="group" value="option2">
   <label for="opt2a">Option 2</label>
</div>
<div>
   <input id="opt3a" type="radio" name="group" value="option3">
   <label for="opt3a">Option 3</label>
</div>
<div style="display:none;">
   <input id="optSecret" type="radio" name="group" value="optionSecret">
   <label for="optSecreta">Secret Option</label>
    
</div>
<div>
   <input id="opt5a" type="radio" name="group" value="option5">
   <label for="opt5a">Option 5</label>
</div>

</td>

<td>
<div>
   <input id="opt1b" type="radio" name="group2" value="option1">
   <label for="opt1b">Option 1</label>
</div>
<div>
   <input id="opt2b" type="radio" name="group2" value="option2">
   <label for="opt2b">Option 2</label>
</div>
<div>
   <input id="opt3b" type="radio" name="group2" value="option3">
   <label for="opt3b">Option 3</label>
</div>
<div style="visibility:hidden;">
   <input id="optSecretb" type="radio" name="group2" value="optionSecret">
   <label for="optSecretb">Secret Option</label>
    
</div>
<div>
   <input id="opt5b" type="radio" name="group2" value="option5">
   <label for="opt5b">Option 5</label>
</div>
</td>

<td>
<div>
   <input id="opt1c" type="radio" name="group3" value="option1">
   <label for="opt1c">Option 1</label>
</div>
<div>
   <input id="opt2c" type="radio" name="group3" value="option2">
   <label for="opt2c">Option 2</label>
</div>
<div>
   <input id="opt3c" type="radio" name="group3" value="option3">
   <label for="opt3c">Option 3</label>
</div>
<div style="position:fixed;opacity:0;">
   <input id="optSecretc" type="radio" name="group3" value="optionSecret">
   <label for="optSecretc">Secret Option</label>
    
</div>
<div>
   <input id="opt5c" type="radio" name="group3" value="option5">
   <label for="opt5c">Option 5</label>
</div>
</td>

</tr>
</table>

Status:

  1. display:none; breaks the cycle over the hidden Radio Button, but collapses the space;
  2. visibility:hidden breaks the cycle over the hidden Radio Button, but preserves the space;
  3. position:fixed;opacity:0 breaks the cycle once (temporary trap) but then resumes after pressing Arrow Up/Down to continue. But normal cycling is still broken.
gene b.
  • 10,512
  • 21
  • 115
  • 227
  • any updates on this? It's the same situation in IE, unfortunately – gene b. Jan 24 '19 at 19:27
  • 1
    I don't know *why* this is the case (I've poked a few people who may know), but I do know it is considered bad practice as far as A11Y concerns go to hide form elements with `display: none`, partially for this very reason. The solutions in the marked duplicate are the way to go. – TylerH Jan 24 '19 at 20:53
  • 1
    No, the suggestions in that thread didn't work. `visibility:hidden` gives the same behavior, only with taking up space. `position:fixed;opacity:0;` keeps the cycle BUT with the trap of temporary loss in that radio button, so you do lose focus and then have to press Down twice. I'd be able to show this if we could re-open the thread. Could you remove your "duplicate" designation and allow me to post further info, this question is still outstanding. – gene b. Jan 27 '19 at 21:52
  • You can still edit the question while it is closed; I'd recommend doing this first. – TylerH Jan 27 '19 at 21:55
  • 1
    Thanks, I updated my answer, you can see all the options demonstrated now -- still no solution. I explained the status. – gene b. Jan 27 '19 at 22:07
  • 2
    I've reopened the question and adjusted its title to reflect your situation a bit more directly. Hopefully you'll get a solution! – TylerH Jan 27 '19 at 22:55

3 Answers3

2

I only solved it with a manual workround, where I intercept the Up/Down keys and make it jump over the hidden element. Works in all 3 browsers (FF/IE/Chrome) and wraps around if necessary. Surprising that a hack is required and no other info is available anywhere.

$('#container').on('keydown', 'input', function(e) {

    var groupname = $(this).attr('name');
    var groupindex = $('[name="' + groupname +  '"]').index($(this));
    var groupsize = $('[name="' + groupname + '"]').length;     

    // For Down Arrow, if subsequent input in group is hidden, focus the one after it (wrap around if necessary)
    if (e.keyCode == 40 && 
        $('[name="' + groupname + '"]').eq(groupindex + 1).length && 
        $('[name="' + groupname + '"]').eq(groupindex + 1).is(':hidden')) 
    {
        e.preventDefault();
        $('[name="' + groupname + '"]').eq((groupindex + 2) % groupsize).focus();
        $('[name="' + groupname + '"]').eq((groupindex + 2) % groupsize).prop('checked', true);
        $('[name="' + groupname + '"]').trigger('change'); // Trigger Change Event manually for any dependencies
        return false;
    }
    // For Up Arrow, if preceding input in group is hidden, focus and select the one before it (wrap around if necessary)
    else if (e.keyCode == 38 && 
            $('[name="' + groupname + '"]').eq(groupindex - 1).length && 
            $('[name="' + groupname + '"]').eq(groupindex - 1).is(':hidden')) 
    {
        e.preventDefault();
        $('[name="' + groupname + '"]').eq((groupindex - 2) % groupsize).focus();
        $('[name="' + groupname + '"]').eq((groupindex - 2) % groupsize).prop('checked', true);
        $('[name="' + groupname + '"]').trigger('change'); // Trigger Change Event manually for any dependencies
        return false;
    }

    return true;
});

Full Demo Snippet

 $('#container').on('keydown', 'input', function(e) {
  
  var groupname = $(this).attr('name');
  var groupindex = $('[name="' + groupname +  '"]').index($(this));
  var groupsize = $('[name="' + groupname + '"]').length;  
  
  // For Down Arrow, if subsequent input in group is hidden, focus the one after it (wrap around if necessary)
  if (e.keyCode == 40 && 
   $('[name="' + groupname + '"]').eq(groupindex + 1).length && 
   $('[name="' + groupname + '"]').eq(groupindex + 1).is(':hidden')) 
  {
   e.preventDefault();
   $('[name="' + groupname + '"]').eq((groupindex + 2) % groupsize).focus();
   $('[name="' + groupname + '"]').eq((groupindex + 2) % groupsize).prop('checked', true);
   $('[name="' + groupname + '"]').trigger('change'); // Trigger Change Event manually for any dependencies
   return false;
  }
  // For Up Arrow, if preceding input in group is hidden, focus and select the one before it (wrap around if necessary)
  else if (e.keyCode == 38 && 
    $('[name="' + groupname + '"]').eq(groupindex - 1).length && 
    $('[name="' + groupname + '"]').eq(groupindex - 1).is(':hidden')) 
  {
   e.preventDefault();
   $('[name="' + groupname + '"]').eq((groupindex - 2) % groupsize).focus();
   $('[name="' + groupname + '"]').eq((groupindex - 2) % groupsize).prop('checked', true);
   $('[name="' + groupname + '"]').trigger('change'); // Trigger Change Event manually for any dependencies
   return false;
  }
  
  return true;
 });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>

<div id="container">

  <div>
      <input type="radio" id="opt1" value="1" name="group">
      <label for="opt1">Option 1</label>
  </div>
  <div>
      <input type="radio" id="opt2" value="2" name="group">
      <label for="opt2">Option 2</label>
  </div>  
  <div>
      <input type="radio" id="opt3" value="3" name="group">
      <label for="opt3">Option 3</label>
  </div>  
  <div style="display:none;">
      <input type="radio" id="optSecret" value="secret" name="group">
      <label for="optSecret">Option Secret</label>
  </div>  
  <div>
      <input type="radio" id="opt5" value="5" name="group">
      <label for="opt5">Option 5</label>
  </div>    

</div>
gene b.
  • 10,512
  • 21
  • 115
  • 227
1

To whomever comes across this and needs a solution for skipping more than one hidden radio at a time like i did i modified the accepted answer to accommodate skipping more than one hidden radio.

$("#container").on('keydown', 'input', function(e) {

        var groupname = $(this).attr('name');
        var group = $('[name="' + groupname +  '"]:visible');
        var groupindex = group.index($(this));
        var groupsize = group.length;

        // For Down Arrow, if subsequent input in group is hidden, focus the one after it (wrap around if necessary)
        if (e.keyCode == 40) {
            e.preventDefault();
            group.eq((groupindex + 1) % groupsize).focus();
            group.eq((groupindex + 1) % groupsize).prop('checked', true);
            group.eq((groupindex + 1) % groupsize).trigger('change'); // Trigger Change Event manually for any dependencies
            return false;
        }
        // For Up Arrow, if preceding input in group is hidden, focus and select the one before it (wrap around if necessary)
        else if (e.keyCode == 38 && group.eq(groupindex - 1).length) {
            e.preventDefault();
            group.eq((groupindex - 1) % groupsize).focus();
            group.eq((groupindex - 1) % groupsize).prop('checked', true);
            group.eq((groupindex - 1) % groupsize).trigger('change'); // Trigger Change Event manually for any dependencies
            return false;
        }
        return true;
    });
Omer Bonfil
  • 417
  • 2
  • 10
1

I ran into this issue recently and I figured out if you set the disabled attribute to true on the radio buttons you want to hide, along with setting display: none, you will be able to use the up/down arrow keys to cycle through the displayed radio buttons in the same radio button group without an issue. This works with Firefox, Chrome, and IE 11 (not sure if it will work with older versions of IE).

Chris
  • 11
  • 1