Custom Select
by Sebastian Kay
HTML
<select>
<button>
<div>
<selectedcontent> </selectedcontent>
<svg width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="m7 10l5 5l5-5z"/>
</svg>
</div>
</button>
<div>
<option value="on">
<!-- custom so it's fully consumed for selectedcontent -->
<div class="custom-option">
<span class="indicator success"></span>
<span class="option-text">On</span>
</div>
</option>
<option value="off">
<div class="custom-option">
<span class="indicator danger"></span>
<span class="option-text">Off</span>
</div>
</option>
<option value="disable">
<div class="custom-option">
<span class="indicator disable"></span>
<span class="option-text">Disable</span>
</div>
</option>
</div>
</select>
<select>
<button>
<div>
<selectedcontent></selectedcontent>
<svg width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="m7 10l5 5l5-5z"/>
</svg>
</div>
</button>
<div>
<option value="Una Kravets">
<div class="custom-option">
<span class="avatar">
<img width="20" height="20" src="https://pbs.twimg.com/profile_images/1587634978308997121/u7009cGe_400x400.jpg" alt="">
</span>
<span class="option-text">Una Kravets</span>
</div>
</option>
<option value="Bramus Van Damme">
<div class="custom-option">
<span class="avatar">
<img width="20" height="20" src="https://pbs.twimg.com/profile_images/1276240813333401600/brd0hSfW_400x400.jpg" alt="">
</span>
<span class="option-text">Bramus Van Damme</span>
</div>
</option>
<option value="Adam Argyle">
<div class="custom-option">
<span class="avatar">
<img width="20" height="20" src="https://pbs.twimg.com/profile_images/1720589781476982784/P9Ld4vC5_400x400.jpg" alt="">
</span>
<span...
CSS
@import "https://unpkg.com/open-props" layer(design.system);
@import "https://unpkg.com/open-props/normalize.min.css" layer(demo.support);
@import "https://unpkg.com/open-props/buttons.min.css" layer(demo.support);
/*
TODO:
- large excellent mobile design
- excellent tablet design
- fix arrow not rotating when open (regression)
- add directional aware entry/exit transitions
*/
select {
/* opt into customizing select */
&, &::picker(select) {
appearance: base-select;
}
font-size: var(--font-size-1);
/* removing open props normalize styles */
background: none;
padding: 0;
/* enable transitions in the drop down */
&::picker(select) {
transition:
opacity .2s ease,
transform .2s var(--ease-out-3),
display .2s allow-discrete,
overlay .2s allow-discrete
;
}
&::picker-icon {
display: none;
}
/* set the off stage styles */
&:not(:open)::picker(select) {
opacity: 0;
transform: scale(.95);
}
/* set the on stage styles */
&:open::picker(select) {
opacity: 1;
transform: scale(1);
}
/* transition the selected option changing */
selectedcontent > * {
transition:
transform 1s var(--ease-spring-4),
display 1s allow-discrete,
opacity 1s;
@starting-style {
opacity: 0;
transform: translateY(10px);
}
opacity: 1;
}
/* customize the invoking button */
> button {
--_text: var(--text-1);
&:focus-visible {
outline-offset: -3px;
}
&:has(selectedcontent) {
align-items: start;
min-inline-size: 20ch;
flex-direction: column;
}
&.primary {
--_bg: var(--link);
--_border: none;
--_text: var(--surface-1);
--_ink-shadow: none;
}
> div {
inline-size: 100%;
display: flex;
justify-content: space-between;
gap: var(--size-3);
}
& > small {
color: var(--text-2);
}
&...
JavaScript
document.body.classList.toggle(
'supports-custom-select',
CSS.supports("appearance: base-select"));
colorspace.oninput = e => {
console.log(colorspace.value);
};