Last Updated: February 25, 2016
·
3.023K
· napotopia

Styling a pseudo-<code>select</code> keeping all native functionality

Say you need to style a select</code> because a designer thought it was a great idea or because a product owner wants a select</code> to looks in a particular way. We all know how difficult it is to have form</code> elements look consistent across browsers or OS's let a alone a select</code>. If you ever ask a developer, how do you style a select</code>? The answer is most likely going to be "you don't".

So how do you style a select without styling it? the idea behind this solution is to add a div absolutely positioned behind the select</code>, set the opacity to 0 on it, and style the element that was just injected. This way you never lose the functionality of a select and do not have to worry about another element triggering a change</code> event.

HTML

<form>
    <fieldset>
        <select class="pseudo-me">
            <option value="Option 1">Option 1</option>
            <option value="Option 2">Option 2</option>
            <option value="Option 3 - A Longer Option">Really Really Long Option</option>
        </select>
    </fieldset>
</form>​

CSS

body { margin:2%; }

select.pseudo-me,
.pseudo-select,
.select-container {
    top:0;
    left:0;
}

select.pseudo-me,
.pseudo-select,
.pseudo-select .down-arrow,
.pseudo-select .down-arrow-container {
    position:absolute;
}

.select-container { position: relative; }

select.pseudo-me {
    opacity:0;
    z-index:2;
}

.pseudo-select { z-index:1; }

.pseudo-select .down-arrow-container {
    top:0;
    right:0;
}

.pseudo-select .down-arrow {
  display: block;
  border-style:solid;
}

JS (requires jQuery)

$('select.pseudo-me').each(function() {

    var $this = $(this),
        BORDER = ($this.css('border-width') == '' || parseInt($this.css('border-width')) == 0) ? '1px solid #000' : $this.css('border'),
        BORDER_COLOR = ($this.css('border-color') === '') ? '#000' : $this.css('border-color'),
        COLOR = $this.css('color'),
        BORDER_RADIUS = ($this.css('border-radius') == '' || parseInt($this.css('border-radius')) == 0) ? '5px' : $this.css('border-radius');

    $this
        .wrap('<div class="select-container"></div>')
        .after('<div class="pseudo-select"><span class="pseudo-select-text"></span><div class="down-arrow-container"><span class="down-arrow"></span></div></div>')
        .on('change', function() {
            $('.pseudo-select-text').text($(this).children('option:selected').text());
        });

    var $downArrow = $('.down-arrow'),
        $downArrowContainer = $('.down-arrow-container');

    $('.pseudo-select, .select-container')
        .width($this.innerWidth())
        .height($this.innerHeight());

    // the down arrow is calculated as a third of the height of the select
    $downArrow
        .height($this.innerHeight())
        .css({'border-width' : $this.innerHeight() / 3,
              'top'          : $this.innerHeight() / 3,
              'right'        : $this.innerHeight() / 6,
              'border-color' : BORDER_COLOR + ' transparent transparent'});

    // setting the height and width the same so that we have proportional spacing based on the select height.
    $downArrowContainer
        .height($this.innerHeight())
        .width($this.innerHeight())
        .css('border-left', BORDER);

    $('.pseudo-select')
        .css({'font-family': $this.css('font-family'),
              'font-size': $this.css('font-size'),
              'padding-left': '5px',
              'line-height': $this.innerHeight() + 'px',
              'border': BORDER,
              'color' : COLOR,
              'border-radius' : BORDER_RADIUS });

    $('.pseudo-select-text').text($this.children('option:selected').text());
});​

A Fiddle showing a working example: http://jsfiddle.net/napotopia/gHDvZ/

This example does a litte bit more that just hide the select</code> and show the faux-select</code> behind it. It gets computed styles and sets default ones when they are not specified. All the styles declared in the CSS portion are for positioning and layout purposes only. Everything that happens on the dynamically generated pseudo-select</code> is either a value retrieved from that element or a calculation most liked based on the border width and border color.

If you change the border, color, or border-radius it will get any of those values and update the pseudo-select</code> accordingly: http://jsfiddle.net/napotopia/gHDvZ/186/

Also to note is the fact that the border and color change on this last example are being applied to the select</code> so the hidden element still gets styled as its faux clone.

I don't really like the idea of all this hackery. My first answer is always don't style a select</code>. Let each device and/or operating system handle how it should look or behave. This hack worked particularly well for a recent hybrid app where a select had to be styled in a specific way but the native functionality was still desired. Use with caution...