Hidden element with visible :after - CSS

ndugger picture ndugger · May 21, 2012 · Viewed 9.8k times · Source

I have recently created a way to style HTML checkboxes and radio buttons, but it requires adding additional elements to it, like a <p> or a <span>. This isn't terrible inconvenient, but when you're styling (CSS) a complete web software, having to go back and rewrite every checkbox can be annoying.

I recently just had a "revelation" about how to style them without using any additional elements, and it works amazingly in Chrome, but in Firefox, well, lets just say it doesn't work.

My HTML:

<input type="checkbox" />

My CSS:

input[type="checkbox"]
{
    visibility:hidden;
}

input[type="checkbox"]:after
{
    visibility:visible;
    content:"W";
    display:block;
    background:#0ab9bf;
    width:20px;
    line-height:20px;
    text-align:center;
    height:20px;
    overflow:hidden;
    text-indent:-100px;
}

input[type="checkbox"]:checked:after
{
    text-indent:0;
}

I was hoping that someone here can help? Keep in mind that I am looking for a strictly CSS solution to this. I will seek a javascript solution only as a last resort. Until then, my hopes are high.

I guess my question is, what is preventing Firefox from showing the "checkbox?" And how can I fix this. Is there a different way to go about doing this?

Answer

Daniel Imms picture Daniel Imms · Mar 18, 2013

I'm guessing it's as David Thomas suggests that the pseudo-element is not visible because of it's 'parent' element. The reason for it working in other browsers could be explained by a different implementation, where the pseudo-elements aren't considered children of the element maybe?

Another method

Anyhow, I've had a bit of experience styling checkboxes and radio buttons like this. A pretty cool trick I've learnt is to hide the <input> off screen and style the associated <label> with some fancy selector tricks. This method leverages the fact that clicking on the <label> will also click the associated <input>.

jsFiddle

HTML

<input type="checkbox" id="check" />
<label for="check"></label>

CSS

input[type="checkbox"] {
    position:absolute;
    left:-9999px;
    top:-9999px;
}

input[type="checkbox"] + label {
    visibility:visible;
    content:"W";
    display:block;
    background:#0ab9bf;
    width:20px;
    line-height:20px;
    text-align:center;
    height:20px;
    overflow:hidden;
}

input[type="checkbox"]:checked + label {
    background:#F00;
}

Improving upon the method

This method uses the a + b selector which means select b which immediately follows a. So the <label> must come after the <input>. A 'safer' way to do this would be to wrap them in a container and style using the general sibling selector a ~ b. The styles will then tolerate the <label> in a different position provided they are still siblings.

<div>
    <input type="checkbox" id="check" />
    <div>It will still work!</div>
    <label for="check"></label>
</div>

Compatibility

Standalone this method is not compatible with IE8 and below because it doesn't support :checked. The usual way to get around this is to add a class .checked, but unfortunately IE8 also has a rendering bug when changing classes and you need a little more hacky JavaScript.

var labels = document.querySelectorAll('label');
for (var i = 0; i < labels.length; i++) {
    labels[i].onclick = handleClick;
}

function handleClick () {
    var checkbox = document.getElementById(this.getAttribute('for'));
    // do something with checkbox.checked
    alert(checkbox.checked);
}

Luckily you shouldn't need to support anything lower than 8 due to its now tiny market share (< 0.77%).