18
votes

I have a custom radio input style that is implemented more or less like this:

<input type="radio" id="testradio" class="visuallyhidden custom-radio">
<label for="testradio">...</label>

.custom-radio + label:before {
  content: ""
  // Styling goes here
}

.custom-radio:focus + label:before {
  outline: 1px solid black;
]

This works great except for one nagging detail: the focus style for keyboard navigation. I can tab-select the group of radio buttons and use the arrow keys to change the selected one, but the default :focus outline doesn't appear because the input tag is hidden.

I tried adding my own focus style as above, but this ends up behaving differently than the default browser styling. By default, Chrome (and other browsers I assume) will draw an outline only when you are keyboard-selecting the radio inputs, not when you click them. However, the CSS :focus style seems to apply when clicking the radio input as well (or in this case, clicking the label), which looks really bad.

Basically my question is this: how do I apply a :focus style to a radio input that fully simulates the default browser behavior, i.e. does not appear from a mouse click focus? Or is there another way I can customize this radio input that will help me preserve the default behavior?

Edit: Here's a JSFiddle demonstrating what I'm talking about. On the first row, you can click a radio button and then navigate with the arrow keys - the outline only appears when you use the keyboard. On the second row, clicking the radio button immediately triggers the focus style.

http://jsfiddle.net/7Ltcks3n/1/

7
I'm using input:focus, but it doesn't behave the same way as what the browser does by default when it comes to mouse clicks.Andrew K
Where is the input:focus on your provided css?LcSalazar
I did it with ".custom-radio:focus + label:before". See updated JSFiddle for a simpler example.Andrew K
How do you make the input visuallyhidden? With display:none? Have you tried something like position:fixed; top: -99px instead?Ilya Streltsyn
You don’t actually have a ] at the end of your second CSS rule, do you?Sebastian Simon

7 Answers

6
votes

I believe this is what you are looking for, my friend. Ability to mimic default browser outlines. You should be able to use the below technique within your code to get the effect.

Demo code below, read more at source article: https://ghinda.net/article/mimic-native-focus-css/

.unreal-focus {
  outline-width: 2px;
  outline-style: solid;
  outline-color: Highlight;
}

/* WebKit gets its native focus styles.
 */
@media (-webkit-min-device-pixel-ratio:0) {
  .unreal-focus {
    outline-color: -webkit-focus-ring-color;
    outline-style: auto;
  }
}
5
votes

The issue is that apparently Blink browsers maintain focus on radio elements after they are clicked, but Firefox and Safari don't.

As a workaround, you could add a class to the document on mousedown and touchstart that hides your added ring, and remove it on keydown.

Working example:

var className = 'focus-radio-hide-ring';
var add = function() {
    document.documentElement.classList.add(className);
};
var remove = function() {
    document.documentElement.classList.remove(className);
};
window.addEventListener('mousedown', add, true);
window.addEventListener('touchstart', add, true);
window.addEventListener('keydown', remove, true);
.custom-radio {
	position: absolute;
	opacity: 0;
}
.custom-radio + label {
	cursor: pointer;
}
.custom-radio + label:before {
	content: "";
	display: inline-block;
	vertical-align: middle;
	width: 0.75em;
	height: 0.75em;
	margin-right: 0.25em;
	background: #EEEEEE;
	border: 1px solid #AAAAAA;
	border-radius: 50%;
}
.custom-radio:checked + label:before {
	background: #6666FF;
}

/* Add styles here: */ 
.custom-radio:focus + label:before {
	box-shadow: 0 0 4px 1px rgba(0, 0, 0, 1);
}
/* Add disable them again here: */ 
.focus-radio-hide-ring .custom-radio:focus + label:before {
	box-shadow: none;
}
<p><input placeholder="Tab from here"></p>

<input id="testradio" type="radio" class="custom-radio">
<label for="testradio">Example</label>
2
votes

I have Faced This Problem so long here i got the solution

https://jsfiddle.net/hahkarthick/nhorqnvt/3/

input:focus, input:hover{
    outline: 2px auto rgb(229, 151, 0);
    outline-offset: -2px;
 }
<input id="inp" type=radio>radio1
<input type=radio>radio2
<input type=radio>radio3
<input type=radio>radio4
<input type=radio>radio5
input:focus, input:hover{
outline: 2px auto rgb(229, 151, 0);
outline-offset: -2px;

}

1
votes

I put together the snippet below based on your original code, not the fiddle example you provided. From what I gather, you are trying to replace the default radio button with your own styled element (label:before).

Since it wasn't specified how the default radios are hidden, I just used opacity: 0 and adjusted the position of the label element to cover it. The same effect can be achieved by moving the radio off the screen: position: absolute; top: -999; left -999; (or something similar).

I tried using display: none and vibility: hidden on the radios, but this is a problem because apparently the focus action isn't triggered when they aren't visible.

I also gave the label:before element some styling just so that it shows up.

To address your issue:

.custom-radio:focus + label:before {
  outline: 1px solid black;
  outline: 1px auto -webkit-focus-ring-color;
}

The outline:0 1px solid black; gives a default outline when the label is clicked. This works fine for FF and IE, but for Chrome, you need to change the color to -webkit-focus-ring-color because it uses a blue color for the radio outline.

.visuallyhidden {
  opacity: 0;
}
.custom-radio + label {
  position: relative;
  left: -25px;
  margin-right: -15px;
}
.custom-radio + label:before {
  content: "X";
  margin: 5px;
  // Styling goes here
}

.custom-radio:focus + label:before {
  outline: 1px solid black;
  outline: 1px auto -webkit-focus-ring-color;
}
<input type="radio" name="testradio" id="testradio1" class="visuallyhidden custom-radio">
<label for="testradio1">radio 1</label>
<input type="radio" name="testradio" id="testradio2" class="visuallyhidden custom-radio">
<label for="testradio2">radio 2</label>
<input type="radio" name="testradio" id="testradio3" class="visuallyhidden custom-radio">
<label for="testradio3">radio 3</label>
0
votes

You can achieve this behavior through scripting. First you need bind keypress event to radio and add the outline style in that handler whenever the radio gets focused or active(by means of adding your custom-radio class to active input). so that you can omit the outline style on mouse click.

0
votes

This will not fix your issue at the moment, but there is actually a selector coming which does exactly what you need: :focusring (https://github.com/w3c/csswg-drafts/pull/709). Right now it only works in Firefox, but should be coming to other browsers as well.

You could use JS like sumankumarg suggests, but you can also use the border of the fake input to show focus state like so: http://jsbin.com/rofawiginu/edit?html,css,output

0
votes

I'm not sure if this is possible in your set up, but a wrapper div if available is very handy in this caase.

This is the html I am working with:

<div class="hvf__form-item hvf__form-item--radio">
  <input type="radio" id="amount-to-give--25" name="hvf-radios-amount" class="hvf__radio" required value="25">
  <label for="amount-to-give--25">€25</label>
</div>

Having the wrapper div allows me to then use :focus-within to add an outline to each radio element.

.hvf__form-item--radio:focus-within {
  outline: 2px dashed var(--color-grey--darkest);
  outline-offset: 4px;
}