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);
};