CSS customizable select playground

Explore the new customizable select surface with rich option content, picker, icon, and checkmark styling, plus an optional fallback for browsers without support.

Ask AI

CSS customizable select playground

Response generated with openai/gpt-5.4-nano. AI can make mistakes. Always review the result.
HTML
<form class="custom-select-demo">
  <label for="language-select">Primary programming language</label>
  <select class="language-select" id="language-select" name="language">
    <button>
      <selectedcontent></selectedcontent>
    </button>
      <option value="javascript" data-language="javascript">
        <span class="language-option" data-language="javascript">
          <span class="logo" data-logo="JS" aria-hidden="true"></span>
          <span class="language-copy">
            <span class="name">JavaScript</span>
            <span class="meta" data-meta="Frontend runtime"></span>
          </span>
        </span>
      </option>
      <option value="typescript" data-language="typescript" selected>
        <span class="language-option" data-language="typescript">
          <span class="logo" data-logo="TS" aria-hidden="true"></span>
          <span class="language-copy">
            <span class="name">TypeScript</span>
            <span class="meta" data-meta="Static typing"></span>
          </span>
        </span>
      </option>
      <option value="python" data-language="python">
        <span class="language-option" data-language="python">
          <span class="logo" data-logo="Py" aria-hidden="true"></span>
          <span class="language-copy">
            <span class="name">Python</span>
            <span class="meta" data-meta="Scripting and data"></span>
          </span>
        </span>
      </option>
      <option value="rust" data-language="rust">
        <span class="language-option" data-language="rust">
          <span class="logo" data-logo="Rs" aria-hidden="true"></span>
          <span class="language-copy">
            <span class="name">Rust</span>
            <span class="meta" data-meta="Safe systems"></span>
          </span>
        </span>
      </option>
      <option value="go" data-language="go">
        <span class="language-option" data-language="go">
          <span class="logo" data-logo="Go" aria-hidden="true"></span>
          <span class="language-copy">
            <span class="name">Go</span>
            <span class="meta" data-meta="Concurrent services"></span>
          </span>
        </span>
      </option>
  </select>
</form>
CSS
.custom-select-demo {
  display: grid;
  gap: 0.55rem;
  inline-size: min(100%, 28rem);
}

.custom-select-demo > label {
  color: color-mix(in oklch, currentColor 72%, transparent);
  font-weight: 750;
}

.language-select {
  inline-size: fit-content;
  max-inline-size: 100%;
  font: inherit;
  color: #f8fafc;
}

@supports (appearance: base-select) {
  .language-select,
  .language-select::picker(select) {
    appearance: base-select;
  }

  .language-select {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-block: 12px;
    padding-inline: 16px;
    border: 1px solid color-mix(in oklch, #8b5cf6 52%, transparent);
    border-radius: 18px;
    background: #111827;
    color: #f8fafc;
    box-shadow: 0 1rem 2.5rem color-mix(in oklch, #8b5cf6 28%, transparent);
  }

  .language-select selectedcontent {
    display: contents;
  }

  .language-select .language-option {
    display: flex;
    align-items: center;
    gap: 10px;
    min-inline-size: max-content;
  }

  .language-select::picker-icon {
    content: '';
    width: 1.5rem;
    aspect-ratio: 1;
    background-image: url('/caret-down.svg');
    background-size: cover;
    filter: brightness(0) invert(1);
    translate: 0.3125rem;
    transition: transform 0.2s ease-out;
  }

  .language-select:open::picker-icon {
    transform: rotateX(180deg);
  }

  .language-select::picker(select) {
    margin-block-start: 0.45rem;
    padding: 0.38rem;
    border: 1px solid color-mix(in oklch, #8b5cf6 46%, transparent);
    border-radius: 18px;
    background: #111827;
    color: #f8fafc;
    box-shadow: 0 1.25rem 3rem color-mix(in oklch, #8b5cf6 32%, black 18%);
    opacity: 0;
    translate: 0 -0.35rem;
    transition: opacity 0.2s ease, translate 0.2s ease, display 0.2s allow-discrete, overlay 0.2s allow-discrete;
  }

  .language-select::picker(select) {
    inline-size: fit-content;
    min-inline-size: anchor-size(width);
    max-inline-size: min(28rem, calc(100vi - 2rem));
  }

  .language-select:open::picker(select) {
    opacity: 1;
    translate: 0 0;
  }

  @starting-style {
    .language-select:open::picker(select) {
      opacity: 0;
      translate: 0 -0.35rem;
    }
  }

  .language-select option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-block: 10px;
    padding-inline: 14px;
    border-radius: 12px;
    background: transparent;
    color: #f8fafc;
    transition: background-color 0.2s ease, color 0.2s ease;
  }

  .language-select option:is(:hover, :focus-visible) {
    background: color-mix(in oklch, #8b5cf6 22%, transparent);
  }

  .language-select option:checked {
    background: color-mix(in oklch, #8b5cf6 34%, transparent);
  }

  .language-select option::checkmark {
    content: '✓';
    display: grid;
    place-items: center;
    inline-size: 1.45rem;
    block-size: 1.45rem;
    flex: none;
    order: 2;
    margin-inline-start: 0.35rem;
    border-radius: 999px;
    background: #8b5cf6;
    color: #111827;
    font-weight: 800;
  }

  .language-select .logo {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: 0.75rem;
    background: var(--logo-bg);
    color: var(--logo-color);
    font-weight: 900;
    letter-spacing: 0;
  }

  .language-select .logo::before {
    content: attr(data-logo);
    font-size: 0.72em;
  }

  .language-select .language-copy {
    display: grid;
    gap: 0.08rem;
    min-inline-size: max-content;
  }

  .language-select .name {
    font-weight: 800;
    line-height: 1.1;
    white-space: nowrap;
  }

  .language-select .meta {
    font-size: 0.78rem;
    color: color-mix(in oklch, currentColor 70%, transparent);
    line-height: 1.1;
    white-space: nowrap;
  }

  .language-select .meta::before {
    content: attr(data-meta);
  }

  .language-select [data-language="javascript"] .logo {
    --logo-bg: #f7df1e;
    --logo-color: #1f2937;
  }

  .language-select [data-language="typescript"] .logo {
    --logo-bg: #3178c6;
    --logo-color: #f8fafc;
  }

  .language-select [data-language="python"] .logo {
    --logo-bg: #3776ab;
    --logo-color: #fff7d6;
  }

  .language-select [data-language="rust"] .logo {
    --logo-bg: #f97316;
    --logo-color: #111827;
  }

  .language-select [data-language="go"] .logo {
    --logo-bg: #00add8;
    --logo-color: #06202a;
  }
}

@supports not (appearance: base-select) {
  .language-select {
    appearance: none;
    padding-block: 12px;
    padding-inline: 16px calc(16px + 1.75rem);
    border: 1px solid color-mix(in oklch, #8b5cf6 52%, transparent);
    border-radius: 18px;
    background: #111827 url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%23f8fafc%22%3E%3Cpath%20stroke%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M18.707%208.293a1%201%200%200%201%200%201.414l-6%206a1%201%200%200%201-1.414%200l-6-6a1%201%200%200%201%201.414-1.414L12%2013.586l5.293-5.293a1%201%200%200%201%201.414%200%22%2F%3E%3C%2Fsvg%3E") right 0.625rem center / 1.5rem no-repeat;
    color: #f8fafc;
  }
}