Making sure your application works the same across all the different browser engines can be a major pain. Recently, I encountered a new battle in one of my projects devpad. This project I decided to not use any UI framework/library, and to rely on the base html elements as much as possible. The <select>
element is probably one we're all familiar with, but it's got some weird behaviour in Safari.
So there I was, happily building using Arc, a chromium-based browser, when I decided (luckily) to check how things looked in Safari:
For comparison, here's the exact same elements on chromium vs safari:
Why did these select elements have this weird, 2008, original iOS look? They looked perfectly fine in my usual browser, what was going on here? After some stack overflow and chatgpt digging around, I finally landed on a solution, but it was quite the tricky road.
The top answer on stack overflow is to use something like this:
select {
-webkit-appearance: none;
}
However, you'll soon find out this removes the little arrow icon, which indicates to your user that it is a dropdown select menu, so not the best thing to remove.
The answer underneath that offers the first little insight into the solution: use a background-image svg, however this is a bit tricky to get to work if you already want to set a background-colour on the element, which I imagine most people do.
Now, I love using lucide.dev icons, so I grabbed the svg with the 'Copy SVG' button, pasted it in, and hey look at that it works! All that you need to change is the stroke colour to whatever you need.
But that when I came across my first issue, changing the colour scheme didn't change the colour of the svg, and you can't use CSS variables in the background-image value. So I tried to put a @media
clause within the select element:
select {
/* disable the gloss effect on safari */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>') no-repeat;
background-size: 18px;
background-position: calc(100% - 3px) 50%;
background-repeat: no-repeat;
background-color: var(--input-background);
padding-right: 24px;
}
@media (prefers-color-scheme: dark) {
select {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>') no-repeat !important;
}
}
I 100% thought this would work, and then I went to the page, set my theme to dark mode... the arrow was still black. Inspecting the element shows the class is being applied to the element, but there is some "Invalid property value" error, despite it being the exact same string as the one that is being shown (in the wrong colour). The only difference is the stroke="white"
instead of stroke="black"
.
No worries, this is where ChatGPT comes in to save the day. By some stroke of genius, it managed to fix it by URL encoding the svg string. Here's the final (working) css classes:
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22black%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Cpath%20d=%22M6%209%2012%2015%2018%209%22/%3E%3C/svg%3E') no-repeat;
background-size: 18px;
background-position: calc(100% - 3px) 50%;
background-repeat: no-repeat;
background-color: var(--input-background);
padding-right: 24px;
}
@media (prefers-color-scheme: dark) {
select {
background-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22white%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Cpath%20d=%22M6%209%2012%2015%2018%209%22/%3E%3C/svg%3E') !important;
}
}
Hopefully, this post can help at least one other person that comes across the same issue and save them a bit of time messing around.
The final product, perfect cross-browser select elements with dark/light mode variants:
If you're interested in the project (devpad) that inspired this, check out my latest blog post