Using Transparent Select Menus To Create Styled Menu Roots
At work, we use Trello for our project management. It's a super light-weight tool that offers an unopinionated approach to task management. But, more than that, it presents some really cool technical choices. One such choice that I wanted to look at was their use of transparent Select menus.
I am a huge fan of using native form elements whenever possible. So when I saw Trello's approach to Select menus, I got excited. They managed to combine the native functionality of the browser's Select element with the high-design of a custom menu root. They do this by overlaying a transparent Select menu on top of a custom menu root. By doing this, it allows the custom root to be visible to the user while still presenting the native Select menu interactions.
To demonstrate, take a look at this code:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Using Transparent Select Menus To Create Styled Menu Roots
</title>
<style type="text/css">
div.menu {
position: relative ;
}
div.menu div.root {
background-color: #FF3399 ;
border-radius: 4px ;
color: #FFFFFF ;
font-size: 30px ;
height: 50px ;
line-height: 50px ;
text-indent: 20px ;
width: 200px ;
}
div.menu select {
border: none ;
cursor: pointer ;
height: 50px ;
left: 0px ;
opacity: 0 ;
position: absolute ;
top: 0px ;
width: 200px ;
}
</style>
</head>
<body>
<h1>
Using Transparent Select Menus To Create Styled Menu Roots
</h1>
<!-- BEGIN: Menu. -->
<div class="menu">
<div class="root">
Menu ( <span class="value"></span> )
</div>
<select name="items">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</div>
<!-- END: Menu. -->
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.0.min.js"></script>
<script type="text/javascript">
$(function() {
var dom = {
menu: $( "div.menu" ),
root: $( "div.root" ),
value: $( "div.root span.value" ),
select: $( "div.menu select" )
};
// Echo the initially selected value.
dom.value.text( dom.select.val() );
// Echo selected value in the menu DOM.
dom.select.change(
function( event ) {
dom.value.text( dom.select.val() );
}
);
});
</script>
</body>
</html>
Notice that the Select menu has "opacity:0" and is positioned absolutely over the custom root element. The opacity allows the Select to be hidden while still accepting mouse interactions. As such, when I go to click on the custom menu root, I end up triggering the native Options menu:
That's pretty cool!
Granted, this kind of combination doesn't work in all situations. But, in some ways, it's the best of both worlds - you get to make the page "pretty" in most situations without having to deal with creating custom dropdowns.
Want to use code from this post? Check out the license.
Reader Comments
I should note that I tested this on all the modern browsers and it's pretty solid until you get back to IE8.
Thank you for this post! Besides the transparent select, I didin't know Trello and I just passed it over my manager.
Thank you Ben!
Nifty idea.
I was actually thinking about some nice select boxes as I did the classic of get all photoshop happy, show client, realise I had no plan on how to implement it :D
@Dani,
It's pretty nice. I come from a Basecamp background and Trello does lack a little bit when it comes to facilitating granular conversation (the way Basecamp does). I have adapted to this by breaking out "rich" features into their own Board where I can really dig in deep and discuss every detail that needs clarification.
So far, it's the best that I've tried.
@Devolved,
Ha ha ha, I know that feeling :D Hopefully something like this can help.
fyi from an a11y perspective there are a number of problems with this approach. This menu is invisible to a screen reader and cannot be accessed by the keyboard.
You can get most of the way by using a <label> element with a tabindex="0" attribute. This allows the label to receive focus, and delegates keypresses to the underlying <select>: http://jsbin.com/lolup/2/edit. But even this is not enough, as you need to add focus styling - and since it's the <select> that actually has focus, you need some JavaScript to make that magic happen. You also need to add ARIA roles so that screen readers know what type of element this and how to handle this.
You can get a sense of the complexity by inspecting the markup jQuery UI's upcoming selectmenu widget uses http://jsfiddle.net/tj_vantoll/Tm3my/. Granted selectmenu replaces the option nodes as well - which adds more complexity - but many of the same concepts still apply.
This is a cool trick, but getting it right from an a11y perspective is unfortunately hard. fwiw Trello is doing it wrong too.
Actually I was able to get the focus styling to work without any JS: http://jsbin.com/lolup/4/edit. That seems to work pretty well on VoiceOver on OS X as well.
@TJ,
I don't know much about screen readers. I was not aware that they took CSS into account when parsing the DOM. I have seen lots of instances of "508" links that appear high in the DOM and have "skip to content" links. These links are generally "display:none". Are screen readers going to ignore those?
@TJ,
Looks pretty cool. I am not familiar with VoiceOver (clearly, I don't know very much about accessibility).
@Ben
Elements with display: none applied are invisible to screen readers. To hide elements from sighted users and keep them accessible to assistive technologies, you need a replacement technique such as http://www.zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement/.
Screen readers do not read CSS, but keyboard users need some visual indication of what element has focus. I had wrist problems for awhile that forced me to use the keyboard exclusively, and I had a hell of a time online. I wrote this about it: http://tjvantoll.com/2013/01/28/stop-messing-with-the-browsers-default-focus-outline/. As an example of the problem, try going to cnn.com and doing anything with the keyboard.
As a general rule of thumb making sure that you can do everything in your UI with the keyboard is an excellent first step to making your UI accessible. Most of the same best practices that make your UI keyboard friendly also make it screen reader friendly. It gets trickier when you get into more complex widgets though.
@Ben, not only do I appreciate this pattern, which I'm sure to find a use for in an upcoming project, but I always learn a little from the way you code too. In this case, I had an aha moment with the var dom = {} object. Brilliant, clean, elegant... all things I love. Thanks for being such a great mentor in many ways. You definitely make the internet a better place by making many of us better programmers.
@TJ,
Thanks for the explanation and the clarity around what screen-readers can / can't see. I'll take a look at Zeldman's article. I can definitely sympathize with the lack of keyboard maneuverability... and it makes me want to go back and rething aspects of my blog :) I know that I have used (and probably built) sites where there is zero indication of what element has "focus" and you basically can't do anything.
Actually, just the other day, I was so flummoxed by the lack of :focus that I started randomly hitting my space bar to see which element would be activated. Suddenly, I started getting dropdown menus showing up :)
@TJ,
... speaking of which, when you TAB away from this comments box, there's almost no indication that the "Submit Comment" button is focused :(
@Chris,
My pleasure! I started using that "dom" pattern when I ran into conflicts with naming. For example, I might have a DOM element that I want to call "save"; but, I may also have a function that I want to call "save" as well:
Now, I can keep my DOM reference short, simple, and _still_ clear without running into naming conflicts. Plus, I just like that all the DOM references are now in one place - I think it helps make the code a bit more clear.
Wow. I wish I'd known this yesterday for the transparent select. Adding a -webkit-appearance: listbox; has allowed me to style a select the way I need it on mobile. It's not perfect but it's close enough.
Also, that dom JS pattern ... concise, easy to use and extremely readable. Definitely going to be trying to use that in the future :)
Thanks for the post!