0Apoyar

Playground de customizable select en CSS

Explora los nuevos select personalizables con contenido enriquecido en las opciones, estilos para el picker, el icono y el checkmark, y un fallback opcional para navegadores sin soporte.

Leer la guía

Preguntar a la IA

Playground de customizable select en CSS

Respuesta generada con openai/gpt-5.4-nano. La IA puede cometer errores. Revisa siempre el resultado.
HTML
<form class="custom-select-demo">
  <label for="language-select">Lenguaje principal</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="Runtime de frontend"></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="Tipado estático"></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 y datos"></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="Sistemas seguros"></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="Servicios concurrentes"></span>
          </span>
        </span>
      </option>
  </select>
</form>
Tailwind CSS
Algunas clases con valores arbitrarios pueden sustituirse por clases nativas, según el valor y la configuración de Tailwind CSS.
<form class="grid gap-[0.55rem] inline-[min(100%,_28rem)]">
  <label class="[color:color-mix(in_oklch,_currentColor_72%,_transparent)] [font-weight:750]" for="language-select">Lenguaje principal</label>
  <select class="inline-fit max-inline-full [font:inherit] text-[#f8fafc] supports-[appearance:base-select]:[appearance:base-select] supports-[appearance:base-select]:[&::picker(select)]:[appearance:base-select] supports-[appearance:base-select]:flex supports-[appearance:base-select]:items-center supports-[appearance:base-select]:gap-[10px] supports-[appearance:base-select]:[padding-block:12px] supports-[appearance:base-select]:[padding-inline:16px] supports-[appearance:base-select]:[border:1px_solid_color-mix(in_oklch,_#8b5cf6_52%,_transparent)] supports-[appearance:base-select]:rounded-[18px] supports-[appearance:base-select]:bg-[#111827] supports-[appearance:base-select]:text-[#f8fafc] supports-[appearance:base-select]:shadow-[0_1rem_2.5rem_color-mix(in_oklch,_#8b5cf6_28%,_transparent)] supports-[appearance:base-select]:[&_selectedcontent]:contents supports-[appearance:base-select]:[&_.language-option]:flex supports-[appearance:base-select]:[&_.language-option]:items-center supports-[appearance:base-select]:[&_.language-option]:gap-[10px] supports-[appearance:base-select]:[&_.language-option]:min-inline-max supports-[appearance:base-select]:[&::picker-icon]:[content:''] supports-[appearance:base-select]:[&::picker-icon]:[display:block] supports-[appearance:base-select]:[&::picker-icon]:inline-[1rem] supports-[appearance:base-select]:[&::picker-icon]:block-[0.65rem] supports-[appearance:base-select]:[&::picker-icon]:flex-none supports-[appearance:base-select]:[&::picker-icon]:bg-current supports-[appearance:base-select]:[&::picker-icon]:[clip-path:polygon(0_0,_50%_58%,_100%_0,_100%_42%,_50%_100%,_0_42%)] supports-[appearance:base-select]:[&::picker-icon]:[translate:0.3125rem] supports-[appearance:base-select]:[&::picker-icon]:[transition:transform_0.2s_ease-out] supports-[appearance:base-select]:[&:open::picker-icon]:[transform:rotateX(180deg)] supports-[appearance:base-select]:[&::picker(select)]:[margin-block-start:0.45rem] supports-[appearance:base-select]:[&::picker(select)]:[padding:0.38rem] supports-[appearance:base-select]:[&::picker(select)]:[border:1px_solid_color-mix(in_oklch,_#8b5cf6_46%,_transparent)] supports-[appearance:base-select]:[&::picker(select)]:rounded-[18px] supports-[appearance:base-select]:[&::picker(select)]:bg-[#111827] supports-[appearance:base-select]:[&::picker(select)]:text-[#f8fafc] supports-[appearance:base-select]:[&::picker(select)]:shadow-[0_1.25rem_3rem_color-mix(in_oklch,_#8b5cf6_32%,_black_18%)] supports-[appearance:base-select]:[&::picker(select)]:opacity-0 supports-[appearance:base-select]:[&::picker(select)]:[translate:0_-0.35rem] supports-[appearance:base-select]:[&::picker(select)]:[transition:opacity_0.2s_ease,_translate_0.2s_ease,_display_0.2s_allow-discrete,_overlay_0.2s_allow-discrete] supports-[appearance:base-select]:[&::picker(select)]:inline-fit supports-[appearance:base-select]:[&::picker(select)]:[min-inline-size:anchor-size(width)] supports-[appearance:base-select]:[&::picker(select)]:[max-inline-size:min(28rem,_calc(100vi_-_2rem))] supports-[appearance:base-select]:[&:open::picker(select)]:opacity-100 supports-[appearance:base-select]:[&:open::picker(select)]:[translate:0_0] supports-[appearance:base-select]:starting:[&:open::picker(select)]:opacity-0 supports-[appearance:base-select]:starting:[&:open::picker(select)]:[translate:0_-0.35rem] supports-[appearance:base-select]:[&_option]:flex supports-[appearance:base-select]:[&_option]:items-center supports-[appearance:base-select]:[&_option]:gap-[10px] supports-[appearance:base-select]:[&_option]:[padding-block:10px] supports-[appearance:base-select]:[&_option]:[padding-inline:14px] supports-[appearance:base-select]:[&_option]:rounded-[12px] supports-[appearance:base-select]:[&_option]:bg-transparent supports-[appearance:base-select]:[&_option]:text-[#f8fafc] supports-[appearance:base-select]:[&_option]:[transition:background-color_0.2s_ease,_color_0.2s_ease] supports-[appearance:base-select]:[&_option:hover]:[background:color-mix(in_oklch,_#8b5cf6_22%,_transparent)] supports-[appearance:base-select]:[&_option:focus-visible]:[background:color-mix(in_oklch,_#8b5cf6_22%,_transparent)] supports-[appearance:base-select]:[&_option:checked]:[background:color-mix(in_oklch,_#8b5cf6_34%,_transparent)] supports-[appearance:base-select]:[&_option::checkmark]:[content:'✓'] supports-[appearance:base-select]:[&_option::checkmark]:grid supports-[appearance:base-select]:[&_option::checkmark]:place-items-center supports-[appearance:base-select]:[&_option::checkmark]:inline-[1.45rem] supports-[appearance:base-select]:[&_option::checkmark]:block-[1.45rem] supports-[appearance:base-select]:[&_option::checkmark]:flex-none supports-[appearance:base-select]:[&_option::checkmark]:order-2 supports-[appearance:base-select]:[&_option::checkmark]:[margin-inline-start:0.35rem] supports-[appearance:base-select]:[&_option::checkmark]:rounded-full supports-[appearance:base-select]:[&_option::checkmark]:bg-[#8b5cf6] supports-[appearance:base-select]:[&_option::checkmark]:text-[#111827] supports-[appearance:base-select]:[&_option::checkmark]:font-extrabold supports-[appearance:base-select]:[&_.logo]:grid supports-[appearance:base-select]:[&_.logo]:place-items-center supports-[appearance:base-select]:[&_.logo]:inline-[34px] supports-[appearance:base-select]:[&_.logo]:block-[34px] supports-[appearance:base-select]:[&_.logo]:rounded-[0.75rem] supports-[appearance:base-select]:[&_.logo]:[background:var(--logo-bg)] supports-[appearance:base-select]:[&_.logo]:[color:var(--logo-color)] supports-[appearance:base-select]:[&_.logo]:font-black supports-[appearance:base-select]:[&_.logo]:tracking-normal supports-[appearance:base-select]:[&_.logo::before]:[content:attr(data-logo)] supports-[appearance:base-select]:[&_.logo::before]:text-[0.72em] supports-[appearance:base-select]:[&_.language-copy]:grid supports-[appearance:base-select]:[&_.language-copy]:gap-[0.08rem] supports-[appearance:base-select]:[&_.language-copy]:min-inline-max supports-[appearance:base-select]:[&_.name]:font-extrabold supports-[appearance:base-select]:[&_.name]:leading-[1.1] supports-[appearance:base-select]:[&_.name]:whitespace-nowrap supports-[appearance:base-select]:[&_.meta]:text-[0.78rem] supports-[appearance:base-select]:[&_.meta]:[color:color-mix(in_oklch,_currentColor_70%,_transparent)] supports-[appearance:base-select]:[&_.meta]:leading-[1.1] supports-[appearance:base-select]:[&_.meta]:whitespace-nowrap supports-[appearance:base-select]:[&_.meta::before]:[content:attr(data-meta)] not-supports-[appearance:base-select]:[appearance:none] not-supports-[appearance:base-select]:[padding-block:12px] not-supports-[appearance:base-select]:[padding-inline:16px_calc(16px_+_1.75rem)] not-supports-[appearance:base-select]:[border:1px_solid_color-mix(in_oklch,_#8b5cf6_52%,_transparent)] not-supports-[appearance:base-select]:rounded-[18px] not-supports-[appearance:base-select]:[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] not-supports-[appearance:base-select]:text-[#f8fafc]" 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 [--logo-bg:#f7df1e] [--logo-color:#1f2937]" data-logo="JS" aria-hidden="true"></span>
      <span class="language-copy">
        <span class="name">JavaScript</span>
        <span class="meta" data-meta="Runtime de frontend"></span>
      </span>
    </span>
  </option>
  <option value="typescript" data-language="typescript" selected>
    <span class="language-option" data-language="typescript">
      <span class="logo [--logo-bg:#3178c6] [--logo-color:#f8fafc]" data-logo="TS" aria-hidden="true"></span>
      <span class="language-copy">
        <span class="name">TypeScript</span>
        <span class="meta" data-meta="Tipado estático"></span>
      </span>
    </span>
  </option>
  <option value="python" data-language="python">
    <span class="language-option" data-language="python">
      <span class="logo [--logo-bg:#3776ab] [--logo-color:#fff7d6]" data-logo="Py" aria-hidden="true"></span>
      <span class="language-copy">
        <span class="name">Python</span>
        <span class="meta" data-meta="Scripting y datos"></span>
      </span>
    </span>
  </option>
  <option value="rust" data-language="rust">
    <span class="language-option" data-language="rust">
      <span class="logo [--logo-bg:#f97316] [--logo-color:#111827]" data-logo="Rs" aria-hidden="true"></span>
      <span class="language-copy">
        <span class="name">Rust</span>
        <span class="meta" data-meta="Sistemas seguros"></span>
      </span>
    </span>
  </option>
  <option value="go" data-language="go">
    <span class="language-option" data-language="go">
      <span class="logo [--logo-bg:#00add8] [--logo-color:#06202a]" data-logo="Go" aria-hidden="true"></span>
      <span class="language-copy">
        <span class="name">Go</span>
        <span class="meta" data-meta="Servicios concurrentes"></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: '';
    display: block;
    inline-size: 1rem;
    block-size: 0.65rem;
    flex: none;
    background: currentColor;
    clip-path: polygon(0 0, 50% 58%, 100% 0, 100% 42%, 50% 100%, 0 42%);
    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;
  }
}

Guía educativa de customizable select

Personalizar un select sin perder su comportamiento nativo

Aprende qué activa base-select, cómo se conectan botón y picker y qué conserva un formulario cuando la decoración no está disponible.

Volver al playground

Modelo mental

El select permite elegir un valor de una lista cerrada. Personalizar su presentación no requiere recrear esa interacción con JavaScript.

Anatomía de un select personalizableEsquema estático con el desplegable abierto. Los números conectan cada pieza con su función; prueba el control real en el ejemplo siguiente.
  1. <button>Superficie que abre el desplegable.
  2. <selectedcontent>Copia del contenido de la opción elegida dentro del botón.
  3. ::picker-iconIndicador de apertura del select.
  4. ::picker(select)Superficie que contiene la lista desplegable.
  5. <option>Cada elección disponible, con su propio value.
  6. ::checkmarkMarca de la opción seleccionada dentro de la lista.

appearance: none elimina parte de la apariencia del sistema, pero no habilita por sí solo el picker personalizable. base-select activa otra presentación del control y del desplegable; aplícalo a ambos. El picker pasa a una superficie web en la capa superior, en lugar de la interfaz propia del sistema móvil.

Piensa en dos superficies: el botón muestra la elección actual y el picker contiene las opciones. ::picker-icon señala la apertura; ::checkmark marca la opción seleccionada. Puedes estilizar cada pieza sin sustituir el select por un conjunto de divs.

Mantén un select convencional si sus opciones de texto y la experiencia del sistema ya resuelven la tarea. La personalización resulta útil cuando necesitas controlar la superficie y presentar contenido adicional; no convierte el campo en un buscador ni permite introducir valores libres.

HTML semántico

Empieza por una etiqueta persistente y opciones comprensibles. Este ejemplo permite elegir un método de entrega y no envía ninguna solicitud.

Método de entrega

Abre el campo y cambia la opción. En un navegador compatible, selectedcontent refleja la elección y el picker recibe los estilos. Sin la mejora, las opciones de texto siguen ofreciendo los mismos valores.
Método de entrega
<form class="guide-delivery">
  <label for="guide-delivery">Método de entrega</label>
  <select id="guide-delivery" name="delivery">
    <button><selectedcontent></selectedcontent></button>
    <option value="standard" selected>Estándar · 3–5 días</option>
    <option value="express">Exprés · 1 día</option>
    <option value="pickup">Recogida en tienda</option>
  </select>
</form>
:root {
  color-scheme: light dark;
}

.guide-delivery {
  display: grid;
  gap: 0.5rem;
  min-inline-size: 0;

  select {
    inline-size: 100%;
    min-inline-size: 0;
    font: inherit;
    padding: 0.75rem;
    color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
    background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 94%, white), color-mix(in oklch, oklch(0.22 0.024 260) 92%, black));
    border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
    border-radius: 16px;
    corner-shape: squircle;

    &:focus-visible {
      outline: 2px solid light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
      outline-offset: 3px;
    }
  }
}

@supports (appearance: base-select) {
  .guide-delivery select {
    appearance: base-select;

    &::picker(select) {
      appearance: base-select;
      font: inherit;
      color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
      background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 94%, white), color-mix(in oklch, oklch(0.22 0.024 260) 92%, black));
      border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
      border-radius: 16px;
      corner-shape: squircle;
      padding: 0.5rem;
      max-inline-size: calc(100vi - 2rem);
    }

    selectedcontent {
      white-space: normal;
      overflow-wrap: anywhere;
    }

    &::picker-icon {
      color: light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    }

    &:open {
      border-color: light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    }

    option {
      padding: 0.75rem;
      white-space: normal;
      overflow-wrap: anywhere;

      &:hover,
      &:focus-visible {
        outline: 2px solid light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
        outline-offset: -2px;
      }

      &:checked {
        font-weight: 800;
      }

      &::checkmark {
        color: light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
      }
    }
  }
}

La etiqueta se asocia mediante for/id. name="delivery" identifica el campo y el value de cada option determina el dato del formulario: al elegir Exprés se obtiene delivery=express, no el texto con el plazo. Traducir la etiqueta no debe cambiar ese contrato.

El primer hijo button contiene selectedcontent, que el navegador rellena con una copia del contenido de la opción elegida. No necesitas sincronizarlo a mano. Si enriqueces las opciones, conserva texto útil, evita IDs que puedan duplicarse al clonar y no introduzcas enlaces o controles interactivos dentro de ellas.

El CSS muestra valores explícitos de color, radio y forma para que puedas reutilizarlo sin los estilos de BaselineLab. light-dark() conserva ambos temas con color-scheme. La preview usa esos mismos valores mediante los tokens del sitio; los controles del playground no modifican el ejemplo.

Estilos y estados

El selector de cada pieza determina dónde actúa la regla. El foco, la apertura y la selección son estados distintos.

select::picker(select)

Selecciona el desplegable, no el botón. Activa aquí también appearance: base-select. El navegador lo asocia al select mediante un ancla implícita y lo coloca en la capa superior; no necesitas añadir popover al HTML ni corregir el apilamiento con z-index.

select::picker-icon

Estiliza el indicador del botón. Mantener una señal de apertura ayuda a reconocer el campo; el ejemplo cambia su color y conserva su forma nativa.

option:checked / option::checkmark

La primera regla describe la opción seleccionada; la segunda, su marca. El ejemplo combina peso de texto con el checkmark, para que la elección no dependa solo del color. Hover o foco no significan que el valor ya haya cambiado.

select:open / select:focus-visible

Usa :open para la presentación mientras el picker está abierto y :focus-visible para un contorno de teclado perceptible. No elimines el foco al decorar el borde. Comprueba también las opciones al recorrerlas con flechas.

La receta no necesita animación. Si añades transiciones de entrada y salida, verifica por separado @starting-style, las transiciones discretas y overlay, y respeta prefers-reduced-motion. El soporte del picker no certifica el de esa receta animada.

Soporte y compatibilidad

La mejora debe conservar una elección útil aunque el navegador no represente todas las piezas modernas.

Consulta el valor base-select de appearance y las tablas de selectedcontent, ::picker(select), ::picker-icon y ::checkmark por separado. El estado general de appearance como propiedad no convierte todos sus valores en interoperables.

@supports (appearance: base-select) limita los estilos de mejora a navegadores que aceptan ese valor. Es una consulta de sintaxis, no una prueba de apertura, clonado o accesibilidad. Comprueba el comportamiento completo en los navegadores objetivo. La base no elimina la apariencia convencional y conserva texto legible en todas las opciones.

Referencia convencional explícita

Este segundo campo usa HTML convencional y no activa base-select. Permite comparar ambas experiencias en tu navegador; no demuestra cómo renderiza la primera receta un motor sin soporte.
Referencia convencional explícita
<form class="guide-delivery-classic">
  <label for="guide-delivery-classic">Método de entrega</label>
  <select id="guide-delivery-classic" name="delivery">
    <option value="standard" selected>Estándar · 3–5 días</option>
    <option value="express">Exprés · 1 día</option>
    <option value="pickup">Recogida en tienda</option>
  </select>
</form>
:root {
  color-scheme: light dark;
}

.guide-delivery-classic {
  display: grid;
  gap: 0.5rem;
  min-inline-size: 0;

  select {
    inline-size: 100%;
    min-inline-size: 0;
    font: inherit;
    padding: 0.75rem;
    color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
    background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 94%, white), color-mix(in oklch, oklch(0.22 0.024 260) 92%, black));
    border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
    border-radius: 16px;
    corner-shape: squircle;

    &:focus-visible {
      outline: 2px solid light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
      outline-offset: 3px;
    }
  }
}

Errores frecuentes

Diagnostica la pieza y el estado antes de añadir más reglas o sustituir el control.

El picker ignora los estilos

Resultado esperado
El botón y la lista comparten la presentación personalizada.
Causa
Solo se activó base-select en el select, o el navegador no implementa la pieza utilizada.
Solución
Actívalo también en ::picker(select); consulta su soporte y prueba apertura y selección reales. No deduzcas soporte completo de CSS.supports().

El contenido seleccionado pierde información

Resultado esperado
La elección se entiende tanto en el botón como en la lista.
Causa
La información depende de decoración o contenido enriquecido que no se conserva en la presentación convencional.
Solución
Incluye un nombre legible en cada option. Usa value estable y selectedcontent para la representación; no uses el texto decorativo como dato del formulario.

Una opción larga desborda

Resultado esperado
El texto sigue siendo legible en una pantalla estrecha.
Causa
base-select deja de dimensionar el control según la opción más larga. Un ancho intrínseco o nowrap puede forzar desbordamiento.
Solución
Define un ancho adaptable para el campo, permite envolver texto y limita el picker al viewport. Prueba todas las elecciones y texto ampliado, no solo la primera.

El control se ve bien pero pierde el foco

Resultado esperado
Se puede abrir, recorrer, elegir y cerrar con teclado.
Causa
Se quitó outline o se añadió lógica que interfiere con el comportamiento nativo.
Solución
Conserva el foco visible. Prueba flechas, selección, Escape y regreso al campo; revisa nombres y anuncios con tecnologías de asistencia en tus entornos objetivo.