Future of CSS: Select styling without the hacks
For years the element has been notoriously difficult to style. Developers had to either accept the browser’s default look or resort to JavaScript-heavy solutions. But why has it been this way for so long? Why Can’t Be Styled? The component is a form control, meaning browsers handle much of its behaviour natively. This includes dropdown logic (have you noticed the options list can overflow the window?), keyboard navigation, and accessibility features. However, because these controls are deeply integrated into the OS, styling has been largely restricted. Workarounds: From jQuery UI to shadcn/ui Since native styling wasn’t an option, developers turned to libraries: jQuery UI (early 2010s): Wrapped in a div, replaced it with a . Custom Dropdowns (2015–2022): React/Vue solutions often replaced entirely. shadcn/ui (modern approach): Uses Radix UI under the hood to create accessible dropdowns. While these solutions worked, they came with trade-offs: extra JavaScript, potential accessibility issues, and performance overhead. Enter base-select: A New Approach with a Caveat With the introduction of the base-select property, browsers will allow full CSS styling of without overriding most native functionality. This means: No need for JavaScript to handle dropdowns. Full control over appearance while keeping built-in accessibility. Potentially faster rendering and better performance. Important Note: The base-select property is currently experimental and only available in Chrome 134+. Browser support is limited, so use this with caution in production environments. Styling with base-select When styling the select element you must add appearance: base-select; to both the select and select::picker(select). select { appearance: base-select; &::picker(select) { appearance: base-select; } } This tells the browser to allow the element to be styled by CSS rather than using the system's default appearance. Understanding ::picker(select): This pseudo-element represents the dropdown listbox (the "picker") of the element. It allows you to style the listbox independently. There are a few different pseudo-classes and pseudo-elements that are exposed to modify the select element. I've documented a few useful ones down below. select { appearance: base-select; /* style the 'button' */ &::picker(select) { appearance: base-select; /* style the 'listbox' */ } &::picker-icon { /* style the 'button' icon */ } &:not(:open) { /* style the 'button' when closed */ &::picker(select) { /* style the 'listbox' when closed */ } } &:open { /* style the 'button' when open */ &::picker(select) { /* style the 'listbox' when open */ } } & option { /* style the options */ &::checkmark { /* style the checkmark on the checked option */ } &:checked { /* style the checked option */ } } } Demo: CSS-Only shadcn/ui Select Let’s recreate shadcn/ui’s select component using only CSS. Here’s how we can do it: Styling the itself to match shadcn/ui’s look. Customising the dropdown’s appearance. Ensuring accessibility is maintained. Remember: This demo only works in Chrome 134+. If you can’t test it, I’ve included a GIF below so you can still see it in action. Markup The HTML structure remains simple. The element allows us to display and style the selected option separately, and we add a chevron icon to mimic shadcn/ui. Fruits Select a Fruit Apple Banana Blueberry Grapes Pineapple Styling the Select Button To start, we need to apply appearance: base-select and set some basic styles. select { appearance: base-select; color: #71717a; background-color: transparent; width: 180px; box-sizing: border-box; padding: 0.5rem 0.75rem; border: 1px solid #e4e4e7; border-radius: calc(0.5rem - 2px); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; } Positioning the content and icon: select > button { display: flex; width: 100%; font-family: inherit; color: currentColor; } select > button > svg { margin: 0 0 0 auto; width: 1.2rem; height: 1.2rem; } Styling the Dropdown Listbox The listbox must be styled separately, ensuring it appears smoothly when opened. We're using the relatively new starting-style to allow us to animate from display: none. select::picker(select) { appearance: base-select; border: 1px solid #e4e4e7; padding: 0.25rem; margin-top: 0.25rem; border-radius: calc(0.5rem - 2px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); cursor: default; transition: opacity 225ms ease-in-out, transform 225ms ease-in-out; transform-origin: top; transform: translateY(0); opacity: 1; @starting-style { transform: translateY(-0.25rem) scale(0.95); opacity: 0; } } Enhancing Accessibi

For years the element has been notoriously difficult to style. Developers had to either accept the browser’s default look or resort to JavaScript-heavy solutions. But why has it been this way for so long?
Why Can’t
Be Styled?
The component is a form control, meaning browsers handle much of its behaviour natively. This includes dropdown logic (have you noticed the options list can overflow the window?), keyboard navigation, and accessibility features. However, because these controls are deeply integrated into the OS, styling has been largely restricted.
Workarounds: From jQuery UI to shadcn/ui
Since native styling wasn’t an option, developers turned to libraries:
-
jQuery UI (early 2010s): Wrapped
in a
div
, replaced it with a
. -
Custom Dropdowns (2015–2022): React/Vue solutions often replaced
entirely.
- shadcn/ui (modern approach): Uses Radix UI under the hood to create accessible dropdowns.
While these solutions worked, they came with trade-offs: extra JavaScript, potential accessibility issues, and performance overhead.
Enter base-select
: A New Approach with a Caveat
With the introduction of the base-select
property, browsers will allow full CSS styling of without overriding most native functionality. This means:
- No need for JavaScript to handle dropdowns.
- Full control over appearance while keeping built-in accessibility.
- Potentially faster rendering and better performance.
Important Note: The base-select
property is currently experimental and only available in Chrome 134+. Browser support is limited, so use this with caution in production environments.
Styling
with base-select
When styling the select element you must add appearance: base-select;
to both the select
and select::picker(select)
.
select {
appearance: base-select;
&::picker(select) {
appearance: base-select;
}
}
This tells the browser to allow the element to be styled by CSS rather than using the system's default appearance.
Understanding ::picker(select)
: This pseudo-element represents the dropdown listbox (the "picker") of the element. It allows you to style the listbox independently.
There are a few different pseudo-classes and pseudo-elements that are exposed to modify the select element. I've documented a few useful ones down below.
select {
appearance: base-select;
/* style the 'button' */
&::picker(select) {
appearance: base-select;
/* style the 'listbox' */
}
&::picker-icon {
/* style the 'button' icon */
}
&:not(:open) {
/* style the 'button' when closed */
&::picker(select) {
/* style the 'listbox' when closed */
}
}
&:open {
/* style the 'button' when open */
&::picker(select) {
/* style the 'listbox' when open */
}
}
& option {
/* style the options */
&::checkmark {
/* style the checkmark on the checked option */
}
&:checked {
/* style the checked option */
}
}
}
Demo: CSS-Only shadcn/ui Select
Let’s recreate shadcn/ui’s select component using only CSS. Here’s how we can do it:
- Styling the
itself to match shadcn/ui’s look.
- Customising the dropdown’s appearance.
- Ensuring accessibility is maintained.
Remember: This demo only works in Chrome 134+. If you can’t test it, I’ve included a GIF below so you can still see it in action.
Markup
The HTML structure remains simple. The
element allows us to display and style the selected option separately, and we add a chevron icon to mimic shadcn/ui.
Fruits
Apple
Banana
Blueberry
Grapes
Pineapple
Styling the Select Button
To start, we need to apply appearance: base-select
and set some basic styles.
select {
appearance: base-select;
color: #71717a;
background-color: transparent;
width: 180px;
box-sizing: border-box;
padding: 0.5rem 0.75rem;
border: 1px solid #e4e4e7;
border-radius: calc(0.5rem - 2px);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
cursor: pointer;
}
Positioning the content and icon:
select > button {
display: flex;
width: 100%;
font-family: inherit;
color: currentColor;
}
select > button > svg {
margin: 0 0 0 auto;
width: 1.2rem;
height: 1.2rem;
}
Styling the Dropdown Listbox
The listbox
must be styled separately, ensuring it appears smoothly when opened. We're using the relatively new starting-style
to allow us to animate from display: none
.
select::picker(select) {
appearance: base-select;
border: 1px solid #e4e4e7;
padding: 0.25rem;
margin-top: 0.25rem;
border-radius: calc(0.5rem - 2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
cursor: default;
transition: opacity 225ms ease-in-out, transform 225ms ease-in-out;
transform-origin: top;
transform: translateY(0);
opacity: 1;
@starting-style {
transform: translateY(-0.25rem) scale(0.95);
opacity: 0;
}
}
Enhancing Accessibility & Interactions
Improve focus visibility and ensure placeholder text stands out:
select:focus-visible {
outline: 2px solid #a1a1aa;
outline-offset: -1px;
}
select:has(option:not([hidden]):checked) {
color: #18181b;
}
Custom Checkmark
We can replace the default checkmark with a custom SVG.
select option::after {
content: "";
width: 1rem;
height: 1.5rem;
margin-left: auto;
opacity: 0;
background: center / contain no-repeat
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2318181b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'%3E%3C/path%3E%3C/svg%3E");
}
select option:checked::after {
opacity: 1;
}
Conclusion
The component is finally getting the flexibility it deserves. With
base-select
, we can create beautiful dropdowns without JavaScript. If you're interested in reading more check out the open ui explainer.
What do you think of base-select? Are you excited to ditch JavaScript-heavy dropdowns?
Thanks for reading! If you'd like to connect, here are my Twitter, BlueSky, and LinkedIn profiles. Come say hi