In this tutorial, I will present you my alternative solution to the select form tag. It’s css-only and it looks simple but really nice. We will use a list of radio elements, styled as a drop-down list, that will look and behave similarly to the select element.
Of course you have to provide some fallback for mobile devices (and IE8 if you wish). I discuss that briefly in the final part of this tutorial.
Check the demo and choose your favorite beer.
Step 1 – HTML
Here is the html we use within a form
<fieldset class="radio-container">
<div class="radio-options">
<div class="toggle">Choose your beer</div>
<ul>
<li>
<input type="radio" name="my-beer" id="choice1" value="choice1">
<label for="choice1">Cul Dorcha</label>
</li>
<li>
<input type="radio" name="my-beer" id="choice2" value="choice2">
<label for="choice2">Rowers Red Ale</label>
</li>
<li>
<input type="radio" name="my-beer" id="choice3" value="choice3">
<label for="choice3">Belfast Ale</label>
</li>
<li>
<input type="radio" name="my-beer" id="choice4" value="choice4">
<label for="choice4">O'Hara Irish Stout</label>
</li>
</ul>
</div>
</fieldset>
Step 2 – The idea
To make things simple I tried to “sketch” my idea. I hope it is clear enough.
Step 3 – CSS
Let’s add some css that reflect the idea presented in the scheme above. For the brevity of this tutorial I omit some parts of the css (e.g. triangle arrows) that only add some visual flavor – you’ll find the complete version in the attached files.
Note that the vendor prefixes are omitted for the same reason.
For the outer containter (“.radio-container”) we’ll have
radio-container {
position: relative;
height: 4em; /* 3em (being the max-height of the inner container) + 1em ("margin") */
}
.radio-container:hover {
z-index: 9999; }
And for the inner one
.radio-options {
position: absolute;
max-height: 3em;
width: 100%;
overflow: hidden;
transition: 0.7s;
}
.radio-options:hover {
max-height: 100em;
}
Next
.radio-options .toggle {
position: relative;
cursor: pointer;
padding: 0.75em;
background: darkgreen;
border-radius: 10px;
z-index: 1; }
/* li are stacked at the same position as .toggle, only .toggle is visible */
.radio-options li {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.radio-options label {
display: block;
opacity: 0;
transition: 0s; }
We hide the inputs, we could just use display : none, but that would not work in browsers (some mobile ones) where clicking the label does not focus the associated input.
.radio-options input {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 3em;
opacity: 0;
z-index:1;
cursor: pointer;
}
Step 4 – What happens on hover – CSS continued
Now let’s look closer at what happens on hover, already : .radio-container gets a high z-index and .radio-options increases its max-height, we’ll add
/* li elements have a normal flow within the .radio-options container */
.radio-options:hover li {
position: relative; }
.radio-options:hover label {
opacity: 1;
transition: 0.5s; }
Step 5 – input:checked
To style the checked option we will use the general sibling selector. It uses a tilde character combinator (E ~ F) and matches elements that are siblings of a given element. The first element (E) has to occur before the second (F) one and they have to share the same parent (li items in our case).
If one of the radio is checked, we’ll see its label instead of the toggle :
.radio-options input:checked ~ label {
position: absolute;
top: 0;
left: 0;
right: 0;
opacity: 1;
/* is above the .toggle so is visible */
z-index: 2;
/* has tha same styles as .toggle */
padding: 0.75em;
background: darkgreen;
border-radius: 10px; }
On hover it returns to the normal flow
.radio-options:hover input:checked ~ label {
position: static;
border-radius: 0; }
Step 6 – What about mobile devices
Since our element is activated on hover you’ll have to provide some fallback for touch devices. One solution is to leave the radio labels visible all the time not only on hover.
Here is my solution to keep the drop-down list, I detect the touch devices with a custom modernizr build and add the following script
$(document).ready(function(){
if (Modernizr.touch) {
$(".radio-options").bind("click", function(event) {
if (!($(this).parent('.radio-container').hasClass("active"))) {
$(this).parent('.radio-container').addClass("active");
event.stopPropagation();
}
});
$(".toggle").bind("click", function(){
$(this).parents('.radio-container').removeClass("active");
return false;
});
}
});
In my css I modify every :hover definition like that
.no-touch .radio-container:hover, .active.radio-container {
z-index: 9999; }
.no-touch .radio-options:hover, .active .radio-options {
max-height: 100em;
}
.no-touch .radio-options:hover li, .active .radio-options li {
position: relative; }
.no-touch .radio-options:hover label, .active .radio-options label {
opacity: 1;
transition: 0.5s; }
....
Step 7 – What about IE8
Again, it’s up to you, the fallback solutions are not the main part of this tutorial. Here is my approach:
<!-- [if (IE 8)]>
<script>
$(document).ready(function(){
$(".radio-options li").bind("click", function() {
$(this).siblings(".checked").removeClass("checked");
$(this).addClass("checked");
});
});
</script>
<![endif]-->
I have to add to my css the .checked class declarations, e.g. (see the attached files for the complete version):
.radio-options .checked label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 0.75em;
background: #1b9e4d;
visibility: visible;
z-index: 2; }
....
That’s all. I hope you’ll find this technique useful, let me know what you think. Thanks.
Terms of use :
You may use the effects demonstrated in tutorials in your own work, both commercial or non-commercial without any attribution. You may not reproduce entire nor large parts of our tutorials. The outcome of our tutorials may not be re-saled nor redistributed.
Great, would be nice to give a way to forward selection responses to the server or something so we can use it as a regular form element.
A native select element will work with a mouse as well as with only a keyboard but alas your alternative won’t (as noticed one of my colleague on Twitter) so it won’t behave similarly to the select element yet.
The relevant accessibility technique is “Ensuring keyboard control for all functionality” ( http://www.w3.org/TR/WCAG20-TECHS/G202.html ).
Also another idea of improvement would be to tag the text that introduces the radio buttons as a group (div.toggle) instead with a legend element and enclose this legend and the group of input[type=”radio”]+ label in a fieldset.
Actually it works perfectly fine with a keyboard. Still, a) you don’t notice, when the element is focused and b) the option list doesn’t flip open. Maybe, these problems could be solved with an additional :focus selector for the radio buttons.
it doesn’t work on android 4.0.4 browser and firefox mobile. in chrome mobile it works as expected
doesn’t work on mobile Safari for iPad.
Hi, Thanks. Are you sure, I tested it on iPad and it worked for me. Have you noticed any particular issues ?
Nice tutorial, but i dont think this is usefull in practice. Uses radio to open select? With that we may have mess in form. Change select option? I must write script to change LI. Etc. This is nice technique to simulate mini menu for example “mini user admin”, but not for simulate select :)
But overall – this technique is usefull (in different situation). Nice work!
Very nice…very handy too…thanks for the great work…
Nice work, selection doesn’t work in IE 8, otherwise very nice.
Opera Mobile said-“I do not understand-what’s up?”
Granted there are some accessibility concerns… but people saying what about form data, if you markup your radio buttons correctly it will pass the same data as a standard select to the server so that’s not an issue.
I like it. Bit of progressive enhancement could see us finally being able to style “select” boxes the way we want without weird browser glitches and lack of CSS support for various aspects of the select box.
Too bad it doesn’t work with touch… Would replacing :hover with :focus work?
Looks nice on desktop though.
Cheers,
Atg
Dumb question:
How do I get links to work with this? I’ve tried wrapping the content inside of the li, around the text, even outside the li’s but nothing works? Right now I have a menu with dysfunctional links…
I’m a student studying interactive design and your tutorials are so inspiring and understandable. thank you for existing!
Very good post! We will be linking to this particularly great article on our site.
Keep up the great writing.
Is realy good job for an computed application but, warning, is not smartphone compatible. The hover can’t works on mobile.