Control deslizante de rango coherente con la marca

Usa la entrada de rango nativa

Usa un <input type="range"> etiquetado en lugar de volver a crear un control deslizante con elementos <div> o controladores de puntero personalizados. El control nativo conserva la interacción con el teclado y el puntero, el comportamiento táctil y la compatibilidad con la accesibilidad. Configura min, max, step y value para el caso de uso.

<label for="range-slider">Volume</label>
<input type="range" id="range-slider" value="70"/>

Si el valor requiere una etiqueta o unidad con formato, exponlo en el contenido del DOM común y actualiza aria-valuetext cuando cambie. No uses ::before ni ::after en pseudoelementos de controles deslizantes, ya que el contenido generado en los controles de formulario no es interoperable. Si se requiere una información sobre la herramienta que siga al control deslizante, fíjalo al control deslizante con el posicionamiento de anclaje de CSS. Consulta Información sobre la herramienta que reconoce la posición para obtener información sobre la herramienta que se mantiene orientada correctamente cuando se gira.

Elige el enfoque de diseño

Para una personalización de color simple, usa formularios coherentes con la marca y conserva la renderización nativa del navegador:

input[type="range"] {
  accent-color: var(--brand-color);
}

accent-color no es una API de diseño de controles deslizantes completa. Su efecto en el control deslizante, la pista y el relleno depende del usuario-agente. Usa los siguientes seudoelementos cuando se deban controlar la pista, el control deslizante, las dimensiones, la forma o el relleno.

Cómo aplicar diseño a la pista y al control deslizante

Restablece el diseño predeterminado del SO con appearance: none, margin: 0 y border: 0. Para normalizar la renderización entre navegadores, declara los pseudoelementos de WebKit y Firefox en bloques de reglas separados y centra el control deslizante de WebKit con margin-block-start: calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2) (Firefox lo centra automáticamente). Mantén los valores para el consumidor como propiedades personalizadas en la entrada para que los temas no se orienten directamente a los seudoelementos.

input[type="range"] {
  --slider-track-size: 0.5rem;
  --slider-thumb-size: 1.25rem;
  --slider-track-radius: 999px;
  --slider-thumb-radius: 50%;
  --slider-track-color: #d9d9d9;
  --slider-fill-color: #06c;
  --slider-thumb-color: var(--slider-fill-color);
  --slider-direction: to right;

  /* reverse gradient direction for right-to-left layouts */
  &:dir(rtl) {
    --slider-direction: to left;
  }

  /* Read min and max from attributes; --control-value is synced by the script below. */
  --attr-min: attr(min type(<number>), 0);
  --attr-max: attr(max type(<number>), 100);
  --control-value: attr(value type(<number>), 50);
  --slider-progress: calc(
    progress(var(--control-value), var(--attr-min), var(--attr-max)) * 100%
  );

  appearance: none;
  inline-size: 100%;
  block-size: 2.5rem; /* Preserve a generous interaction area. */
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  cursor: pointer;
}

/* WebKit/Blink. */
input[type="range"]::-webkit-slider-runnable-track {
  block-size: var(--slider-track-size);
  border: 0;
  border-radius: var(--slider-track-radius);
  background: linear-gradient(
    var(--slider-direction),
    var(--slider-fill-color) var(--slider-progress),
    var(--slider-track-color) var(--slider-progress)
  );
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  inline-size: var(--slider-thumb-size);
  block-size: var(--slider-thumb-size);
  margin-block-start: calc(
    (var(--slider-track-size) - var(--slider-thumb-size)) / 2
  );
  border: 0;
  border-radius: var(--slider-thumb-radius);
  background: var(--slider-thumb-color);
}

/* Firefox. */
input[type="range"]::-moz-range-track {
  block-size: var(--slider-track-size);
  border: 0;
  border-radius: var(--slider-track-radius);
  background: var(--slider-track-color);
}

input[type="range"]::-moz-range-progress {
  block-size: var(--slider-track-size);
  border-radius: var(--slider-track-radius);
  background: var(--slider-fill-color);
}

input[type="range"]::-moz-range-thumb {
  inline-size: var(--slider-thumb-size);
  block-size: var(--slider-thumb-size);
  border: 0;
  border-radius: var(--slider-thumb-radius);
  background: var(--slider-thumb-color);
}

Proporciona siempre un indicador visible del enfoque del teclado después de quitar la apariencia nativa. No uses solo el color:

input[type="range"]:focus-visible {
  outline: 2px solid var(--slider-fill-color);
  outline-offset: 4px;
}

Cómo aplicar diseño a la ruta activa

Cuando la parte completada de la pista tiene un estilo diferente de la pista sin completar, Firefox la expone a través de ::-moz-range-progress. WebKit/Blink no proporcionan un seudoelemento equivalente, por lo que debes sincronizar el valor de entrada en vivo con --control-value en los eventos input:

const slider = document.querySelector('#range-slider');

function syncSliderValue() {
  slider.style.setProperty('--control-value', slider.value);
}

syncSliderValue();
slider.addEventListener('input', syncSliderValue);

Limitaciones

  • No combines los seudoselectores de WebKit y Firefox en una sola lista separada por comas (p.ej., ::-webkit-slider-thumb, ::-moz-range-thumb); los navegadores descartan toda la regla si se encuentra un seudoelemento de proveedor no reconocido.

Protocolos de contingencia

Resguardos y compatibilidad de navegadores con accent-color

La propiedad accent-color es una mejora progresiva. Los navegadores que no admitan esta propiedad la ignorarán y usarán los colores predeterminados de su IU.

Resguardos y compatibilidad de navegadores para progress() y attr()

Cuando no se admite progress(), calcula --slider-progress con calc():

@supports not (opacity: progress(1, 0, 100)) {
  input[type="range"] {
    --slider-progress: calc(
      (var(--control-value) - var(--attr-min)) /
      (var(--attr-max) - var(--attr-min)) * 100%
    );
  }
}

Cuando no se admite el tipo attr(), sincroniza --attr-min y --attr-max de los atributos min y max de la entrada en JavaScript:

function syncSliderValue() {
  slider.style.setProperty('--attr-min', slider.min || 0);
  slider.style.setProperty('--attr-max', slider.max || 100);
  slider.style.setProperty('--control-value', slider.value);
}