problem with <select> and :after with CSS in WebKit
Asked Answered
B

12

157

I would like to add some style on a select box with the pseudo :after (to style my select box with 2 parts and without images). Here's the HTML:

<select name="">
  <option value="">Test</option>
</select>

And it doesn't work. I don't know why and I didn't find the answer in the W3C specs. Here's the CSS:

select {
  -webkit-appearance: none;
  background: black;
  border: none;
  border-radius: 0;
  color: white;
}

select:after {
  content: " ";
  display: inline-block;
  width: 24px; height: 24px;
  background: blue;
}

So is it normal or is there a trick?

Balzac answered 20/8, 2010 at 16:2 Comment(3)
I imagine that this would not, should not be possible, otherwise it would make it too easy to change the contents of forms. Imagine a simple bit of CSS changing the display price for something. It could be shown as cheaper than it really is, tricking people into buying something for more than they expected. (Or course, if someone has access to the CSS of a page/userstylesheet, then they could probably do the same in other ways…)Slip
@Slip - This is incorrect, the author of the page should have complete control over all aspects of page styling. Browser manufacturers trying to prevent styling of certain elements because they might be used to mislead users is not going to be effective as there are nearly limitless number of ways to trick the system or produce false positives.Westernize
Possible duplicate of Pseudo elements and SELECT tagCountermove
P
180

I haven't checked this extensively, but I'm under the impression that this isn't (yet?) possible, due to the way in which select elements are generated by the OS on which the browser runs, rather than the browser itself.

Paleolithic answered 20/8, 2010 at 18:33 Comment(13)
It's 2019 now and it's still the sameRapparee
It's 2020 now and it's still the sameBaines
Check out CSS Houdini if you'd like to have custom select/checkbox in CSS. The only issue with it is that the browsers support is not great yet. Slowly, but things are getting better.Potable
It's 2021 now and it's still the sameNod
It's 2021 now and I remember IE6 had this sort of problem. Relied on the OS implementation for SELECT. Had to use IFRAME's to ensure a SELECT was hidden by modal div's. Just remember folks... the developers saved hours and hours of work by reusing this one OS component. Hours and hours.Synthesis
It's 2029 now and John Connor leads the Resistance the North American Resistance to launch an all-out assault on Skynet's defense grid..Portal
Obligatory "It's 2022 now and it's still the same" ...Pyrognostics
(Prediction) It's 2083 now and it's still the samePotherb
It's 2022 now and it's still the sameWed
@AbhishekKumar Déjà vu :)Realist
It's 2023 now and it's still the sameDotdotage
It's 2024 now and it's still the sameConfraternity
It's 14:27 now and it's still the sameGoodman
S
74

I was looking for the same thing since the background of my select is the same as the arrow color. As previously mentioned, it is impossible yet to add anything using :before or :after on a select element. My solution was to create a wrapper element on which I added the following :before code.

.select-wrapper {
    position: relative;
}

.select-wrapper:before {
    content: '\f0d7';
    font-family: FontAwesome;
    color: #fff;
    display: inline-block;
    position: absolute;
    right: 20px;
    top: 15px;
    pointer-events: none;
}

And this my select

select {
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    width: 100%;
    padding: 10px 20px;
    background: #000;
    color: #fff;
    border: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

select::-ms-expand {
    display: none;
}

I have used FontAwesome.io for my new arrow, but you can use whatever else you want. Obviously this is not a perfect solution, but depending on your needs it might be enough.

Synopsis answered 29/8, 2014 at 18:32 Comment(5)
This is visually perfect. But you cant click through that icon and select element wont open :/ Any suggestions?Unmannerly
pointer-events: none; should take care of that. There was a problem with my code above. I'll change any sass above to css to remove any confusion.Synopsis
Does is matter which pseudo selector (before vs after) is used?Strontium
You can use one or the otherSynopsis
This is a great solution but when implementing it I found that for some reason :before worked and NOT :after. I don't know why.Hultgren
D
37

To my experience it simply does not work, unless you are willing to wrap your <select> in some wrapper. But what you can do instead is to use background image SVG. E.g.

    .archive .options select.opt {
    -moz-appearance: none;
    -webkit-appearance: none;
    padding-right: 1.25EM;
    appearance: none;
    position: relative;
    background-color: transparent;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10px' width='15px'%3E%3Ctext x='0' y='10' fill='gray'%3E%E2%96%BE%3C/text%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-size: 1.5EM 1EM;
    background-position: right center;
    background-clip: border-box;
    -moz-background-clip: border-box;
    -webkit-background-clip: border-box;
}

    .archive .options select.opt::-ms-expand {
        display: none;
    }

Just be careful with proper URL-encoding because of IE. You must use charset=utf8 (not just utf8), don't use double-quotes (") to delimit SVG attribute values, use apostrophes (') instead to simplify your life. URL-encode s (%3E). In case you havee to print any non-ASCII characters you have to obtain their UTF-8 representation (e.g. BabelMap can help you with that) and then provide that representation in URL-encoded form - e.g. for ▾ (U+25BE BLACK DOWN-POINTING SMALL TRIANGLE) UTF-8 representation is \xE2\x96\xBE which is %E2%96%BE when URL-encoded.

Dobson answered 19/2, 2017 at 0:51 Comment(4)
This is a nice and elegant solutionJehoshaphat
This is a fantastic, simple, and elegant solution that requires no extra HTML which is exactly what I was looking for! Thanks! This should be the chosen answer IMO.Cyanogen
This is the answer I was looking for. The only issue I'm having is that I can only seem to change the fill to a word like 'black' or 'white' or 'gray'. When I try anything like '#fff' or even just #fff it goes away.Auberbach
perfect. it is what I needNikitanikki
Z
21

What if modifying the markup isn't an option?

Here's a solution that has no requirements for a wrapper: it uses an SVG in a background-image. You may need to use an HTML entity decoder to understand how to change the fill colour.

-moz-appearance: none;
-webkit-appearance: none;
appearance: none;

background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23000000%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E');
background-repeat: no-repeat;
background-position: right .7em top 50%;
background-size: .65em auto;

Pinched from CSS-Tricks.

Zebrass answered 2/7, 2019 at 10:56 Comment(2)
This is a nice trick. Super annoying that we still can't style selects in 2020.Fussbudget
IMHO, this should be the selected answer: it most closely satisfies the OP's question and results in the least code.Sclaff
E
16

This post may help http://bavotasan.com/2011/style-select-box-using-only-css/

He is using a outside div with a class for resolving this issue.

<div class="styled-select">
  <select>
    <option>Here is the first option</option>
    <option>The second option</option>
  </select>
</div>
Enyedy answered 14/5, 2013 at 22:0 Comment(0)
H
15

Faced the same problem. Probably it could be a solution:

<select id="select-1">
    <option>One</option>
    <option>Two</option>
    <option>Three</option>
</select>
<label for="select-1"></label>

#select-1 {
    ...
}

#select-1 + label:after {
    ...
}
Heading answered 15/1, 2013 at 18:14 Comment(1)
Can you add in the styles required to make the label sit over top of the select element?Anemone
L
9

This solution is similar to the one from sroy, but with css triangle instead of web font:

.select-wrapper {
  position: relative;
  width: 200px;
}
.select-wrapper:after {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid #666;
  position: absolute;
  right: 8px;
  top: 8px;
  pointer-events: none;
}
select {
  background: #eee;
  border: 0 !important;
  border-radius: 0;
  -webkit-appearance:none;
  -moz-appearance:none;
  appearance:none;
  text-indent: 0.01px;
  text-overflow: "";
  font-size: inherit;
  line-height: inherit;
  width: 100%;
}
select::-ms-expand {
  display: none;
}
<div class="select-wrapper">
  <select>
    <option value="1">option 1</option>
    <option value="2">option 2</option>
    <option value="3">option 3</option>
  </select>
</div>
Linebreeding answered 1/7, 2016 at 3:52 Comment(0)
C
5

This is a modern solution I cooked up using font-awesome. Vendor extensions have been omitted for brevity.

HTML

<fieldset>
    <label for="color">Select Color</label>
    <div class="select-wrapper">
        <select id="color">
            <option>Red</option>
            <option>Blue</option>
            <option>Yellow</option>
        </select>
        <i class="fa fa-chevron-down"></i>
    </div>
</fieldset>

SCSS

fieldset {
    .select-wrapper {
        position: relative;

        select {
            appearance: none;
            position: relative;
            z-index: 1;
            background: transparent;

            + i {
                position: absolute;
                top: 40%;
                right: 15px;
            }
        }
    }

If your select element has a defined background color, then this won't work as this snippet essentially places the Chevron icon behind the select element (to allow clicking on top of the icon to still initiate the select action).

However, you can style the select-wrapper to the same size as the select element and style its background to achieve the same effect.

Check out my CodePen for a working demo that shows this bit of code on both a dark and light themed select box using a regular label and a "placeholder" label and other cleaned up styles such as borders and widths.

P.S. This is an answer I had posted to another, duplicate question earlier this year.

Countermove answered 8/11, 2016 at 19:42 Comment(0)
R
2

If you chose to use the select::after method, remember that:

.select::after{
       ...
       pointer-events: none;
       ...

for clickable..

Rebuke answered 12/6, 2022 at 21:4 Comment(0)
V
1
<div class="select">
<select name="you_are" id="dropdown" class="selection">
<option value="0" disabled selected>Select</option>
<option value="1">Student</option>
<option value="2">Full-time Job</option>
<option value="2">Part-time Job</option>
<option value="3">Job-Seeker</option>
<option value="4">Nothing Yet</option>
</select>
</div>

Insted of styling the select why dont you add a div out-side the select.

and style then in CSS

.select{
    width: 100%;
    height: 45px;
    position: relative;
}
.select::after{
    content: '\f0d7';
    position: absolute;
    top: 0px;
    right: 10px;
    font-family: 'Font Awesome 5 Free';
    font-weight: 900;
    color: #0b660b;
    font-size: 45px;
    z-index: 2;

}
#dropdown{
    -webkit-appearance: button;
       -moz-appearance: button;
            appearance: button;
            height: 45px;
            width: 100%;
        outline: none;
        border: none;
        border-bottom: 2px solid #0b660b;
        font-size: 20px;
        background-color: #0b660b23;
        box-sizing: border-box;
        padding-left: 10px;
        padding-right: 10px;
}
Vietnamese answered 18/4, 2019 at 12:3 Comment(1)
clicking on the :after element then doesn't activate the selectTilburg
J
1

Instead of a wrapper element you can use CSS grid and place an icon (or whatever) in the same cell as the select:

.select-field {
  display: grid;
  grid-template:
    "label"
    "select"
    / max-content;
}

.label {
  grid-area: label;
}

.select {
  appearance: none;
  background: white;
  border: 1px solid var(--border-color);
  grid-area: select;
  padding-block: 0.5ex;
  padding-inline: 1ch calc(1ch + 1em);
}

.after {
  align-self: center;
  border-block-start: 0.5em solid var(--border-color);
  border-inline: 0.5em solid transparent;
  block-size: 0;
  grid-area: select;
  inline-size: 0;
  justify-self: end;
  margin-inline-end: 1ch;
  pointer-events: none;
}

.select,
.select + .after {
  --border-color: silver;
}

.select:hover,
.select:hover + .after {
  --border-color: grey;
}

.select:focus,
.select:focus + .after {
  --border-color: rebeccapurple;
}
<div class="select-field">
  <label for="my-select" class="label">Select One</label>

  <select id="my-select" class="select">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
    <option value="baz">Baz</option>
  </select>
  
  <div class="after"></div>
</div>

Here I used an empty div to and styled it to be a CSS triangle which has the same color as the border which changes during hover/focus.

The most important bits here are the following:

  • The <select> and the <div class="after"> go into the same grid-area (which I named select). This will put the empty div over the select.
  • Give the <select> an appearance of none. This will remove any browser default style.
  • Give the <select> and extra padding at the end of the inline direction to make room for the empty style.
  • Justify the empty div to the end.
  • Give the empty div an extra margin at the end of the inline direction which matches your desired padding of the <select>
  • Give the empty div a pointer-events of none so the click will go through it to the <select> element.

Other then that you can do whatever with the empty div. It doesn’t even have to be empty. E.g. you can put an svg icon in there if you want.

Joelynn answered 28/11, 2021 at 4:53 Comment(1)
How is this "instead of a wrapper?" Not only a wrapper but another div besides... 🤔Rasla
R
0

Look ma, no wrappers or images.

Using gradients to draw the arrow. Big advantage for me is that it is simpler to theme based on other design colors vs. requiring an image or embedding SVGs in the HTML just to update the fill style.

Based off of "https://mcmap.net/q/111534/-how-do-i-change-the-select-box-arrow-duplicate/28274325#28274325" which also has some other . It took me an embarrassingly long time to figure out what's going on in there and just get the basics out of it, so hopefully this helps someone else.

CodePen version.

(If you're wondering about the color name used here, see https://developer.mozilla.org/en-US/docs/Web/CSS/system-color )

select {
  /* reset (add vendor prefixed versions if you need) */
  box-sizing: border-box;
    appearance: none;
  margin: 0;
  
  /* Other styles, not required. */
  padding: 0.25em;
  
  /* The arrow -- settings */
    --arrow-pad: .1em;    /* space between arrow and border */
    --arrow-size: .5em;   /* width and height of arrow */
    --arrow-color: GrayText;  /* arrow color */
  
    --arrow-offset: calc(var(--arrow-pad) + var(--arrow-size));  /* D.R.Y. */
  
  /* Add space for arrow. */
    padding-right: calc(var(--arrow-offset) * 4) !important;

  /* The arrow - draw using 2 45° gradients with sharp edges. */
  /* Can change angles for a non-square arrow or otherwise get creative here. */
  background-image:
    linear-gradient(45deg,  transparent        50%, var(--arrow-color) 50%),  /* left part of arrow */
    linear-gradient(135deg, var(--arrow-color) 50%, transparent        50%);  /* right part of arrow */
    
  /* Position the arrow on the right side taking desired padding and arrow width into account. */
  background-position:
    calc(100% - (var(--arrow-offset) * 1.5)) 50%,  /* left part of arrow */
    calc(100% - var(--arrow-offset))         50%;  /* right part of arrow */
  
  /* Declare the actual sizing of the arrow "image." */
  background-size:
    var(--arrow-size) var(--arrow-size),  /* left part of arrow */
    var(--arrow-size) var(--arrow-size);  /* right part of arrow */
  
  background-repeat: no-repeat;  /* just one arrow, please */
}
  <select id="my-select" class="select">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
    <option value="baz">Baz</option>
  </select>
  
Rasla answered 22/12, 2023 at 23:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.