Pagination
ProntoNavegação entre páginas de uma listagem grande. Mostra contexto (página atual / total + counts), atalhos para extremos.
Usar quando
Tabela com 50+ items. Listas onde scroll infinito não cabe (auditoria, exportação). Resultados de busca.
Não usar quando
Lista pequena (<20 itens — mostre tudo). Feed contínuo (use scroll infinito). Wizard (use Stepper).
Variantes
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Page change | click no número | Background ink + texto branco no novo, sumiço suave do antigo | 150ms ease |
| Hover button | mouseenter | Background → surfaceHover | 100ms ease |
| Disabled state | page=1 ou page=last | opacity 0.4 + cursor not-allowed nos prev/first ou next/last | instant |
| Range jump | click "..." | Pula 5 páginas (ou abre input modal pra digitar destino) | instant |
Acessibilidade
Acessibilidade — checklist
Teclado
| Tab | Foco nos botões em sequência |
| Enter / Space | Vai para a página |
| ← / → | Anterior/próximo (com role=navigation) |
ARIA esperado
- <nav aria-label="Paginação">
- role="navigation"
- aria-current="page" no número da página atual
- aria-label="Ir para página X" em cada botão de número
- aria-label="Página anterior/próxima" nos chevrons
Notas
- aria-current="page" no número atual.
- Disabled state em prev/first se page=1 (não esconda).
- Em mobile: oculte numeração e mostre input "Página X de Y".
- Sempre exiba contagem total ("1-20 de 247").
Código
'use client';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { T, RADIUS } from '@/lib/tokens';
interface Props { page: number; totalPages: number; onChange: (p: number) => void }
export function Pagination({ page, totalPages, onChange }: Props) {
return (
<nav aria-label="Paginação" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<button onClick={() => onChange(page - 1)} disabled={page === 1} aria-label="Página anterior">
<ChevronLeft size={14} />
</button>
{/* … botões de página … */}
<button onClick={() => onChange(page + 1)} disabled={page === totalPages} aria-label="Próxima página">
<ChevronRight size={14} />
</button>
</nav>
);
}Regras
Faça
- ✓aria-current="page" no número atual.
- ✓Mostre contagem ("1-20 de 247") sempre.
- ✓Disabled state em extremos (não esconda).
- ✓Em mobile: input direto na página + Y total.
- ✓Range "..." jump 5 páginas ou input para grandes.
Não faça
- ✗Não esconda prev/next em extremos (mostre disabled).
- ✗Não force scroll infinito sem dar opção de paginar.
- ✗Não tenha mais de 7 botões numerados visíveis.
- ✗Não esqueça contagem total.
- ✗Não use cor brass nos botões (ink/surface basta).
Tokens usados
| Token | Valor | Papel |
|---|---|---|
T.ink | #0A0A0A | background da página atual |
T.surface | #FFFFFF | background dos outros |
T.surfaceHover | #F7F5EC | hover |
RADIUS.md | 12 | borderRadius |