tabIndex doesn't make a label focusable using Tab key

Html

Html Problem Overview


I'm trying to replace checkbox/radio inputs with icons. For this, I need to hide the original checkbox/radio. The problem is, I also want the form to properly support keyboard input, i.e. let the input remain focusable by Tab key and selectable using Spacebar. Since I'm hiding the input, it cannot be focused, so instead, I'm trying to make its <label> focusable.

This documentation and various other sources led me to believe I can do that using tabindex attribute (corresponding to HTMLElement.tabIndex property). However, when I try to assign tabindex to my label, it remains as unfocused as ever, however much I try to Tab to it.

Why doesn't tabindex make the label focusable?

The following snippet demonstrates the issue. If you focus the input with your mouse and try focusing the label using Tab, it doesn't work (it focuses the following <span> with tabindex instead).

document.getElementById('checkbox').addEventListener('change', function (event) {
  document.getElementById('val').innerHTML = event.target.checked;
});

<div>
  <input type="text" value="input">
</div>
<div>
  <label tabindex="0">
    <input type="checkbox" id="checkbox" style="display:none;">
    checkbox: <span id="val">false</span>
  </label>
</div>
<span tabindex="0">span with tabindex</span>

(The JavaScript code just allows to see that clicking on the label properly (un)checks the checkbox.)

Html Solutions


Solution 1 - Html

> Why doesn't tabindex make the label focusable?

Short Answer:

  1. Label is focusable.
  2. TabIndex won't make any difference.
  3. Welcome to the world of browser/agent inconsistencies.

tl;dr;

The label (Ref) element is very much focusable. Its DOM Interface is HTMLLabelElement which derives from HTMLElement (Ref) which in turn implements GlobalEventHandlers (Ref) and hence exposes the focus() method and onfocus event handler.

The reason you are unable to get hold of proper specification / reference document for labels focus behaviour, is because you might have been looking at HTML5 Specs. Interestingly, HTML5 refs do not state anything relating to that, which adds to the confusion.

This is mentioned in the HTML 4.01 Ref here: http://www.w3.org/TR/html401/interact/forms.html#h-17.9.1

Specifically near the end of section 17.9.1 and just before 17.10:

> When a LABEL element receives focus, it passes the focus on to its > associated control.

Also, elsewhere (I am unable to get hold of that part of the ref) I have read that it depends on the implementing agent. (Don't take my word for that, am not too sure).

However, what it means is that when you focus a label (or a label received a focus), that focus is passed on to its associated labeleable control. This will not result in two different focuses, but one focus on the input (in your case a checkbox). Because of this behaviour, tabindex property cannot play a role.

There is also a test suite by W3C for website accessibility (WAAG) here: http://www.w3.org/WAI/UA/TS/html401/cp0102/0102-ONFOCUS-ONBLUR-LABEL.html which, discusses the implementation of onfocus and onblur for a label. Ideally a keyboard or an assistive technology that emulates the keyboard should implement this. But...

This is where the browser inconsistencies play their role.

This can be demonstrated by this example. Check the following snippet in different browsers. (I have tested it against IE-11, GC-39 and FF-34. All of them behave differently.)

  1. Click the button "Focus Label"
  2. It should focus the label, then pass the focus and highlight its associated checkbox outline in blue.
  3. Chrome-v39 works. IE-v11 it doesn't (somehow html and body do respond to :focus). FF-v34 it works.

Talking about browser inconsistencies, try using the "access key" L. Some browsers will focus the checkbox whereas some will click it i.e. pass the action to it.

Here is a fiddle to test it: http://jsfiddle.net/abhitalks/ff0xds4z/2/

Here is a snippet:

label = $("label").first();
$("#btn").on("click", function() {
    label.focus();
});

* { margin: 8px; }
.highlight { background-color: yellow; }
:focus {
    outline: 2px solid blue;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="txt" type="text" value="input" /><br />
<label for="chk" accesskey="L">Checkbox: </label>
<input id="chk" type="checkbox" /><br />
<input id="btn" type="button" value="Focus Label" />

Hope that clears up your doubts.

.


##Your problem:

Now focussing (sic) on your original problem of not being able to focus a label, because you want to style a checkbox differently by placing an icon kind of thing in its place.

In order to do that, one option for you is to not hide it completely by doing a display:none;. Rather, make it 1x1 pixel and shove it under your icon. This way it will still receive focus naturally and yet be effectively hidden.

For example, if your icons are a checkmark and a cross, then change the position of the checkbox and make the icons out of ::before or ::after pseudo-elements on the label. That will cause the checkbox to still receive focus, and make the icon respond to that. That will give the apparent illusion of the icon taking the focus.

Demo Fiddle: http://jsfiddle.net/abhitalks/v0vxcw77/

Snippet:

div.chkGroup { position: relative; }
input#chk {
    position: absolute;
    width: 1px; height: 1px;
    margin: 0; margin-top: 4px; outline: none;
    border: 1px solid transparent; background-color: transparent;
}
label::before {
    content: '\2714';
    position: relative;
    width: 18px; height: 18px;
    background-color: #fff;
    margin-right: 8px; padding: 2px;
    display: inline-block; 
    border: 1px solid transparent;
}
input#chk:checked + label::before {
    content: '\2716';
}
input#chk:focus + label::before {
    border: 1px solid #00f;
}

<input id="txt" type="text" value="input" /><br /><br />
<div class="chkGroup">
    <input id="chk" type="checkbox" />
    <label for="chk" accesskey="L">Checkbox</label>
</div>

.

Solution 2 - Html

Edit: The following was a misreading of the spec:

> Looking that the full > specification, > you'll see that there is something called tabindex focus > flag, > which defines if the tabindex attribute will actually make the field > "tabbable". The label element is missing from that list of suggested > elements. > > But then again, so is the span element, so go figure :).

That said, yYou can make the label text focusable by wrapping the whole thing in an another element, or using some JavaScript to force the issue. Unfortunately, wrapping (here in an anchor) can men a fair amount of extra work in CSS and JS to get working like a normal label element.

document.getElementById('checkbox').addEventListener('change', function(event) {
  document.getElementById('val').innerHTML = event.target.checked;
});
document.getElementsByClassName('label')[0].addEventListener('click', function(event) {
  event.target.getElementsByTagName('label')[0].click();
  event.preventDefault();
});
document.getElementsByClassName('label')[0].addEventListener('keypress', function(event) {
  if ((event.key || event.which || event.keyCode) === 32) {
    event.target.getElementsByTagName('label')[0].click();
    event.preventDefault();
  }
});

.label,
.label:visited,
.label:hover,
.label:active {
  text-decoration: none;
  color: black;
}

<div>
  <input type="text" value="input">
</div>
<div>
  <a class="label" href="#">
    <label tabindex="0">
      <input type="checkbox" id="checkbox" style="display:none;">checkbox: <span id="val">false</span>
    </label>
  </a>
</div>
<span tabindex="0">span with tabindex</span>

Solution 3 - Html

As previous posters said: Label focus always goes directly to the input element.

Quite an annoyance if somebody has fancy (but fake) checkboxes, hiding the original ones, with an actual focus for keyboard navigation nowhere to be seen.

best solution I can think of: javascript.

Style-away the actual focus, in favor of a fake one:

	input[type=checkbox]:focus {
		outline: none;
	}
	.pseudo-focus {
		outline: 2px solid blue;
	}

and watch for changes on the (in many scenarios visibly hidden) original checkbox:

	$('input[type=checkbox')
		.focus( function() {
			$(this).closest('label').addClass('pseudo-focus');
		})
		.blur( function() {
			$(this).closest('label').removeClass('pseudo-focus');    
		});

Full jsfiddle here.

Solution 4 - Html

Since this old post is one of the top google results for html label tabindex I want to add my very simple working solution. As @Abhitalks mentioned in the accepted answer, the focus of a label is passed to it's associated control. So to bypass this behavior, just add a tabindex to the label and use event.preventDefault() in a focus EventListener.

@Heretic Monkey kind of had the right idea in his answer but you don't need a wrapper element to achieve this. You will, however, need to manually forward any required keystrokes (like spacebar) through.

For example:

'use strict';

let field = document.getElementById('hidden-file-chooser');
let label = document.querySelector('label[for=hidden-file-chooser]');

// prevent focus passing
label.addEventListener('focus', event => {
  event.preventDefault();
});

// activate using spacebar
label.addEventListener('keyup', event => {
  if (event.keyCode == 32) {
    field.click();
  }
});

#hidden-file-chooser {
  display: none;
}

input[type=text] {
  display: block;
  width: 20rem;
  padding: 0.5rem;
}

label[for=hidden-file-chooser] {
  display: inline-block;
  background: deepskyblue;
  margin: 1rem;
  padding: 0.5rem 1rem;
  border: 0;
  border-radius: 0.2rem;
  box-shadow: 0 0 0.5rem 0 rgba(0,0,0,0.7);
  cursor: pointer;
}

<input type="text" placeholder="Click here and start tabbing through ...">

<input id="hidden-file-chooser" type="file">
<label for="hidden-file-chooser" tabindex="0"> Select a File </label>

<input type="text" placeholder="... then shift+tab to go back.">

P.S: I used input[type=file] in my example because that's what I was working on when I ran across this issue. The same principles apply to any input type.

Solution 5 - Html

For input type radio or checkbox:

opacity: 0;
height: 0;
width: 0;
min-height: 0;
line-height: 0;
margin: 0;
padding: 0;
border: 0 none;

and the Js above does the trick sweetly.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionhon2aView Question on Stackoverflow
Solution 1 - HtmlAbhitalksView Answer on Stackoverflow
Solution 2 - HtmlHeretic MonkeyView Answer on Stackoverflow
Solution 3 - HtmlFrank NockeView Answer on Stackoverflow
Solution 4 - HtmlBesworksView Answer on Stackoverflow
Solution 5 - HtmlFemi OjemuyiwaView Answer on Stackoverflow