106

Is it possible to change the default background color of a select list option on hover?

HTML:

<select id="select">
    <option value="1">One</option>
    <option value="2">Two</option>
    <option value="3">Three</option>
</select>

I have tried option:hover { background-color: red; }, but it is of no use. Does anybody know how to do this?

doppelgreener
  • 4,809
  • 10
  • 46
  • 63
Gopesh
  • 3,882
  • 11
  • 37
  • 52
  • 4
    In some browsers it's possible, in others it's not. Usually form elements are rendered by the OS and not the browser, so the CSS isn't always (or even 'is rarely') applied. – David Thomas May 07 '12 at 14:46
  • 1
    in which browsers is it possible? – Vipin Verma Sep 16 '13 at 09:27
  • ***[changing the select to multiple selects using size which is triggered onclick. Once converted to a multiple select you can then style the option hover.](https://stackoverflow.com/a/64612416/7745445)*** – mohan mu Nov 01 '21 at 16:32

13 Answers13

46

This can be done by implementing an inset box shadow. eg:

select.decorated option:hover {
    box-shadow: 0 0 10px 100px #1882A8 inset;
}

Here, .decorated is a class assigned to the select box.

Hope you got the point.

doppelgreener
  • 4,809
  • 10
  • 46
  • 63
user2964504
  • 501
  • 4
  • 2
  • 11
    +1 Great idea! But `option:checked` works better than `option:hover`. Both only seem to work only on Firefox, though. – Oriol Apr 15 '14 at 19:38
  • 3
    My approach was just to make a completely different element that behaved like a 'select' element. Basically a div with a list inside. – Diogo Oct 20 '15 at 09:42
  • 11
    2+ years old, but doesn't work on Chrome in late 2017. option:checked will work, but option:hover doesn't. Best work around is likely creating your own implementation of a checkbox list, so the base OS doesn't interfere with styles. – AlwaysTalkingAboutMyDog Nov 26 '17 at 02:30
28

Select / Option elements are rendered by the OS, not HTML. You cannot change the style for these elements.

Priyesh
  • 399
  • 4
  • 3
23

This way we can do this with minimal changes :)

option:hover {
  background-color: yellow;
}
<select onfocus='this.size=10;' onblur='this.size=0;' onchange='this.size=1; this.blur();'>
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="opel">Opel</option>
  <option value="audi">Audi</option>
</select>
YakovL
  • 7,557
  • 12
  • 62
  • 102
Krishnaraj
  • 485
  • 4
  • 4
  • 2
    This works, but could you please add an explianation of how it works, and maybe remove the duplicate option groups, which hinders understanding. I deleted the onfocus, onchange, and onblur, and it still worked. The only line taking affect is "option:hover{background-color:yellow;}" – run_the_race Jun 09 '20 at 08:53
  • 52
    This doesn't work for me at all. I don't know why it's working in the code snippet above. – ewomack Oct 12 '20 at 18:33
  • 7
    This is a nice idea. Like, I find others are curious of how this works. Well, the idea is like this. See a single select tag cannot be styled as the control goes to system. Here what is done is on click, the select gets changed to multiple select using size. There is no restriction on multiple select, so the style works. Brilliant. – Balu Oct 16 '20 at 13:12
  • This is the best option so far for me, since I don't have to make a new control... I actually made a pseudocombo past year, using UL+LI+CSS+Javascript, but there's always better to do the simplest. Thank you very much. – quinqui Jan 27 '21 at 00:16
  • What an interesting take on a select option hover. Technically you're never actually seeing the options as normally shown when you open a single-line select element... Did you ever figure out a way to get around the fact that it pushes other surrounding content around when it resizes/expands? – Dan Overlander Feb 16 '22 at 20:07
  • how is this working? – user151496 May 31 '23 at 14:02
  • @user151496 @ewomack The inline js that he wrote is the important part: `onfocus='this.size=10;' onblur='this.size=0;' onchange='this.size=1; this.blur();'`. Without that the snippet also does not work. – Mqx Jun 01 '23 at 11:28
12

Implementing an inset box shadow CSS works on Firefox:

select option:checked,
select option:hover {
    box-shadow: 0 0 10px 100px #000 inset;
}

Checked option item works in Chrome:

select:focus > option:checked { 
    background: #000 !important;
}

There is test on https://codepen.io/egle/pen/zzOKLe

For me this is working on Google Chrome Version 76.0.3809.100 (Official Build) (64-bit)

Newest article I have found about this issue by Chris Coyier (Oct 28, 2019) https://css-tricks.com/the-current-state-of-styling-selects-in-2019/

Egle Kreivyte
  • 332
  • 3
  • 7
9

The problem is that even JavaScript does not see the option element being hovered. This is just to put emphasis on how it's not going to be solved (any time soon at least) by using just CSS:

window.onmouseover = function(event)
{
 console.log(event.target.nodeName);
}

The only way to resolve this issue (besides waiting a millennia for browser vendors to fix bugs, let alone one that afflicts what you're trying to do) is to replace the drop-down menu with your own HTML/XML using JavaScript. This would likely involve the use of replacing the select element with a ul element and the use of a radio input element per li element.

John
  • 1
  • 13
  • 98
  • 177
2

Adding/toggling size attributes on focus event as suggestest by @Krishnaraj works pretty well on desktop using mouse controls.

However, the previous answers don't work well with keyboard controls.

The following example wraps the aforementioned state toggling into a javaScript helper function and adds additional event listeners for better accessibility

setSelectHover();

function setSelectHover(selector = "select") {
  let selects = document.querySelectorAll(selector);
  selects.forEach((select) => {
    let selectWrap = select.parentNode.closest(".select-wrap");
    // wrap select element if not previously wrapped
    if (!selectWrap) {
      selectWrap = document.createElement("div");
      selectWrap.classList.add("select-wrap");
      select.parentNode.insertBefore(selectWrap, select);
      selectWrap.appendChild(select);
    }
    // set expanded height according to options
    let size = select.querySelectorAll("option").length;

    // adjust height on resize
    const getSelectHeight = () => {
      selectWrap.style.height = "auto";
      let selectHeight = select.getBoundingClientRect();
      selectWrap.style.height = selectHeight.height + "px";
    };
    getSelectHeight(select);
    window.addEventListener("resize", (e) => {
      getSelectHeight(select);
    });

    /**
     * focus and click events will coincide
     * adding a delay via setTimeout() enables the handling of
     * clicks events after the select is focused
     */
    let hasFocus = false;
    select.addEventListener("focus", (e) => {
      select.setAttribute("size", size);
      setTimeout(() => {
        hasFocus = true;
      }, 150);
    });

    // close select if already expanded via focus event
    select.addEventListener("click", (e) => {
      if (hasFocus) {
        select.blur();
        hasFocus = false;
      }
    });

    // close select if selection was set via keyboard controls
    select.addEventListener("keydown", (e) => {
      if (e.key === "Enter") {
        select.removeAttribute("size");
        select.blur();
      }
    });

    // collapse select
    select.addEventListener("blur", (e) => {
      select.removeAttribute("size");
      hasFocus = false;
    });
  });
}
body {
  font-size: 10vmin;
}

select {
  --selectHoverCol: #999;
  --selectedCol: red;
  width: 100%;
  font-size: 1em;
  padding: 0.3em;
  background-color: #fff;
}

select:focus {
  border-radius: 0px;
  border-color: red;
  background: #fff;
  outline: none;
}

.select-wrap {
  position: relative;
}

.select-wrap:focus-within select {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 10
}

option:hover {
  background-color: var(--selectHoverCol);
  color: #fff;
}

option:checked {
  box-shadow: 0 0 1em 100px var(--selectedCol) inset;
}
<select class="selectHovercolor">
  <option value="volvo" selected>Volvo</option>
  <option value="saab">Saab</option>
  <option value="opel">Opel</option>
  <option value="audi">Audi</option>
</select>
<p>paragraph</p>
<select>
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="opel">Opel</option>
  <option value="audi">Audi</option>
  <option value="Lexus">Lexus</option>
  <option value="Mercedes">Mercedes</option>
</select>
<p>paragraph</p>
  • The select field is wrapped in a <div> with relative position.
  • :focus-within pseudo state toggles the select positioning between absolute and initial (static) – this way we can avoid layout shifts.
  • the wraping div height is recalculated on resize
  • since the focus event coincides with with the click event, we add a delay for click events, triggering the select field to collapse after selection
  • If an option was selected via keyboard controls and selection was confirmed by pressing "enter" - the size attribute is removed.
  • size attribute is set according to the actual number of <option> elements
herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
1

Select / Option elements are rendered by the OS/Client, not HTML. You cannot change the style for these elements in modern Browser.

On older clients

select option:checked,
select option:hover {
    box-shadow: 0 0 10px 100px #000 inset;
}

Checked option item works in older Chrome:

select:focus > option:checked { 
   background: #000 !important;
}
MaZzIMo24
  • 139
  • 1
  • 6
0

You can do this, just know that it will change all of the select inputs throughout the html, it doesn't change the blue hover, but it does style everything else.

option {
  background: #1b1a1a !important;
  color: #357b1d !important;
}
select {
  background: #1b1a1a !important;
  color: #357b1d !important;
}

// If you also want to theme your text inputs:
input {
  background: #1b1a1a !important;
  color: #357b1d !important;
}
Fritz
  • 343
  • 1
  • 10
0
<html>

<head>
  <style>
    option:hover {
      background-color: yellow;
    }
  </style>
</head>

<body>
  <select onfocus='this.size=10;' onblur='this.size=0;' onchange='this.size=1; this.blur();'>
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="opel">Opel</option>
    <option value="audi">Audi</option>
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="opel">Opel</option>
    <option value="audi">Audi</option>
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="opel">Opel</option>
    <option value="audi">Audi</option>
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="opel">Opel</option>
    <option value="audi">Audi</option>
  </select>

</body>

</html>
0

This worked for me in chrome!

<select onfocus='this.size=10;'>                                    
<option>Crossing</option>
<option>Crossing Up</option>
<option>Crossing Down</option>
</select>

<style>
select option:hover {
  box-shadow: 0 0 10px 100px green inset;
  color:white;
}
select option:checked{
  box-shadow: 0 0 10px 100px green inset;
  }
</style>

However, the checked option's background will remain same even if i hover on another option

By the way, you can do that one as well. Here is the link for that: https://www.w3schools.com/howto/howto_custom_select.asp

Abhishek
  • 546
  • 5
  • 13
0

I would consider switching from a <select> element to a <div> list, like below:

https://jsfiddle.net/getbutterfly/gquh02dz/

This will make it cross-browser compatible. Every other method using CSS appearance tricks and <select> dropdowns is hacky.

HTML

<div class="sel">
    <div class="label">Select option...</div>
    <div class="options">
        <div>Option 1</div>
        <div>Option 2</div>
        <div>Option 3</div>
        <div>Lot of text to display, so it can expand multiple lines and expand the select main text also</div>
    </div>
</div>

JavaScript

const sel = document.querySelector('.sel');
const label = document.querySelector('.label');
const options = document.querySelector('.options');

options.setAttribute('hidden', true);

sel.addEventListener('click', (e) => {
    e.stopPropagation();
    options.removeAttribute('hidden');
});

document.body.addEventListener('click', (e) => {
    options.setAttribute('hidden', true);
});

options.addEventListener('click', (e) => {
    if (e.target.tagName === 'DIV') {
        e.stopPropagation();
        label.textContent = e.target.textContent;
        e.target.classList.add('selected');
        Array.from(e.target.parentNode.children).forEach((child) => {
            if (child !== e.target) {
                child.classList.remove('selected');
            }
        });
        options.setAttribute('hidden', true);
    }
});

CSS

* {
    box-sizing: border-box;
}
.sel {
    color: #000000;
    width: 250px;
    box-sizing: border-box;
    background-color: #ffffff;
    border: 1px solid #000000;
    overflow: hidden;
    background: url("data:image/svg+xml,<svg height='10px' width='10px' viewBox='0 0 16 16' fill='%23000000' xmlns='http://www.w3.org/2000/svg'><path d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/></svg>") no-repeat calc(100% - 10px) 14px;

}
.label,
.sel .options div {
    padding: 10px;
}
.selected {
    background-color: #ff0000;
}
.sel .options {
    width: 250px;
    background-color: #ffffff;
}
.sel .options div:hover {
    background-color: #00ff00;
}

With a bit of extra CSS, the dropdown can be animated and the selected text can be truncated to fit inside a fixed height, behaving exactly like a <select> element.

Ciprian
  • 872
  • 1
  • 10
  • 30
-5

I realise this is an older question, but I recently came across this need and came up with the following solution using jQuery and CSS:

jQuery('select[name*="lstDestinations"] option').hover(
        function() {
            jQuery(this).addClass('highlight');
        }, function() {
            jQuery(this).removeClass('highlight');
        }
    );

and the css:

.highlight {
    background-color:#333;
    cursor:pointer;
}

Perhaps this helps someone else.

maxx777
  • 1,320
  • 1
  • 20
  • 37
  • 1
    This doesn't work, simply because the background-color _gets overwritten_ by the operating system. This is equivalent to just using `option:hover`. Not quite though: your script breaks if I start using the keyboard while I have the mouse hovering over an option, so it is not robust. – doppelgreener May 01 '15 at 07:18
-10

this is what you need, the child combinator:

    select>option:hover
    {
        color: #1B517E;
        cursor: pointer;
    }

Try it, works perfect.

Here's the reference: http://www.w3schools.com/css/css_combinators.asp

Dani
  • 9
  • 1
  • 2
    In Firefox, the default style is `option:checked { background-color: -moz-html-cellhighlight !important; color: -moz-html-cellhighlighttext !important; }`. Then, the problem is that you can't override an internal important property, even if you use !important. – Oriol Apr 15 '14 at 19:24