Slider
ProntoControle deslizante para escolher valor numérico em range contínuo. Single ou range (2 thumbs). Suporta steps.
Usar quando
Volume, brilho, percentual, range de preço/data, qualquer numero contínuo onde preview visual ajuda.
Não usar quando
Valor preciso (use Number Input). Lista de opções (use Select). Mais de 2 thumbs (UI complexa demais).
Variantes
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Thumb drag | mousedown + mousemove | Thumb segue cursor, valor atualiza inline | follows pointer |
| Thumb scale on grab | mousedown | Thumb scale 1→1.15 | 100ms ease |
| Track fill | value muda | Cor preenche da esquerda até thumb | 150ms ease |
| Tick snap | drag perto de tick | Snap visual + sutil haptic feedback | instant |
| Keyboard step | ↑↓ ou ←→ | Move pelo step (instant) | instant |
| Tooltip with value | drag | Mostra value flutuante acima do thumb | 150ms fade |
Acessibilidade
Acessibilidade — checklist
Teclado
| Tab | Foco no thumb |
| ← / ↓ | Diminui pelo step |
| → / ↑ | Aumenta pelo step |
| PgUp / PgDn | Step × 10 |
| Home / End | Min / Max |
ARIA esperado
- role="slider"
- aria-valuenow / valuemin / valuemax / valuetext
- aria-label="Volume" ou aria-labelledby
- aria-orientation="horizontal" (default)
Notas
- Use aria-valuetext quando value precisa de unit ("15%", "R$ 250").
- Range slider: 2 thumbs cada com role="slider" próprio.
- Mostre valor sempre (label, tooltip ou ao lado).
- Steps tornam keyboard previsível.
Código
'use client';
import { useState } from 'react';
import { T } from '@/lib/tokens';
export function Slider({ value, onChange, min = 0, max = 100, step = 1 }) {
const pct = ((value - min) / (max - min)) * 100;
return (
<div style={{ position: 'relative', height: 24, width: '100%' }}>
<div style={{ position: 'absolute', top: 11, left: 0, right: 0, height: 2, background: T.surface3, borderRadius: 980 }}>
<div style={{ width: `${pct}%`, height: '100%', background: T.ink, borderRadius: 980 }} />
</div>
<input type="range" role="slider"
min={min} max={max} step={step} value={value}
onChange={(e) => onChange(Number(e.target.value))}
aria-valuenow={value} aria-valuemin={min} aria-valuemax={max}
style={{ position: 'absolute', inset: 0, opacity: 0 }} />
<div style={{
position: 'absolute', top: 4, left: `calc(${pct}% - 8px)`,
width: 16, height: 16, borderRadius: 980, background: T.surface,
border: `2px solid ${T.ink}`, boxShadow: T.shadowSm,
}} />
</div>
);
}Regras
Faça
- ✓Mostre valor visível (label ou tooltip).
- ✓Track 2-3px height; thumb 14-16px.
- ✓Cor preenchida ink à esquerda, surface3 à direita.
- ✓Steps com ticks visuais se discreto.
- ✓Keyboard ←→ ↑↓ obrigatório.
Não faça
- ✗Não use slider para valor preciso (use Number Input).
- ✗Não use mais de 2 thumbs (range OK, mais que isso vira ruído).
- ✗Não esconda valor (sem feedback de quanto está).
- ✗Não force thumb pequeno < 14px (touch difícil).
- ✗Não use cor brass na track (cor é sinal).
Tokens usados
| Token | Valor | Papel |
|---|---|---|
T.ink | #0A0A0A | borda do thumb + fill da track |
T.surface | #FFFFFF | background do thumb |
T.surface3 | #F3F0E6 | track sem preenchimento |
T.shadowSm | 0 1px 2px ... | sombra do thumb |
RADIUS.pill | 980 | thumb e track |