Usar a entrada de intervalo nativa
Use um <input type="range"> rotulado em vez de recriar um controle deslizante com elementos <div> ou gerenciadores de ponteiro personalizados. O controle nativo preserva a interação com o teclado e o ponteiro, o comportamento de toque e o suporte à acessibilidade. Configure min, max, step e value para o caso de uso.
<label for="range-slider">Volume</label>
<input type="range" id="range-slider" value="70"/>
Se o valor exigir um rótulo ou uma unidade formatada, exponha-o no conteúdo DOM comum e atualize aria-valuetext quando houver mudanças. Não use ::before ou ::after em pseudoelementos de controle deslizante, já que o conteúdo gerado em controles de formulário não é interoperável. Se for necessário que uma dica siga o controle deslizante, use o posicionamento de âncora CSS para fixá-la ao controle. Consulte Dicas com reconhecimento de posição para dicas que mantêm a orientação correta ao serem invertidas.
Escolher a abordagem de estilo
Para uma personalização de cores simples, use formulários consistentes com a marca e mantenha a renderização nativa do navegador:
input[type="range"] {
accent-color: var(--brand-color);
}
accent-color não é uma API completa de estilo de controle deslizante. O efeito no controle deslizante, na faixa e no preenchimento depende do user agent. Use os pseudoelementos abaixo quando a faixa, o controle, as dimensões, a forma ou o preenchimento precisarem ser controlados.
Estilizar a faixa e o controle deslizante
Redefina o estilo padrão do SO com appearance: none, margin: 0 e border: 0. Para normalizar a renderização entre navegadores, declare os pseudoelementos do WebKit e do Firefox em blocos de regras separados e centralize o controle deslizante do WebKit usando margin-block-start: calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2) (o Firefox centraliza o controle deslizante automaticamente). Mantenha os valores voltados ao consumidor como propriedades personalizadas na entrada para que os temas não segmentem pseudoelementos diretamente.
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);
}
Sempre forneça um indicador visível de foco do teclado depois de remover a aparência nativa. Não use apenas cores:
input[type="range"]:focus-visible {
outline: 2px solid var(--slider-fill-color);
outline-offset: 4px;
}
Estilizar o rastreamento de atividades
Quando a parte preenchida da faixa tem um estilo diferente da parte não preenchida, o Firefox a expõe usando ::-moz-range-progress. O WebKit/Blink não fornece um pseudoelemento equivalente. Portanto, sincronize o valor de entrada em tempo real com --control-value em eventos input:
const slider = document.querySelector('#range-slider');
function syncSliderValue() {
slider.style.setProperty('--control-value', slider.value);
}
syncSliderValue();
slider.addEventListener('input', syncSliderValue);
Limitações
- Não combine pseudosseletores do WebKit e do Firefox em uma única lista separada por vírgulas (por exemplo,
::-webkit-slider-thumb, ::-moz-range-thumb). Os navegadores descartam toda a regra se um pseudoelemento de fornecedor não reconhecido for encontrado.
Substitutos
Alternativas e suporte do navegador para accent-color
A propriedade accent-color é um aprimoramento progressivo.
Os navegadores que não são compatíveis com essa propriedade vão ignorá-la e usar as cores padrão da interface.
Alternativas e suporte do navegador para progress() e attr()
Quando não houver suporte para progress(), calcule --slider-progress com 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%
);
}
}
Quando o attr() digitado não é compatível, sincronize --attr-min e --attr-max dos atributos min e max da entrada em 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);
}