-
Notifications
You must be signed in to change notification settings - Fork 3
pesquisa componente pagination
Premissa de escopo: este documento trata da pagination clássica — navegação por números de página (ou anterior/próximo) para dividir um conjunto de itens em páginas discretas, tipicamente no rodapé de uma lista, tabela ou grade de resultados. Não cobre "infinite scroll" nem "load more" (tratados como alternativas na seção 1), embora ambos sejam mencionados como comparação.
Pagination é o componente de navegação que divide um conjunto grande de itens (resultados de busca, linhas de tabela, produtos, posts) em páginas menores e discretas, e oferece controles para mover-se entre elas — números de página, anterior/próximo, primeira/última.
O problema que resolve é duplo: performance (evita carregar/renderizar milhares de itens de uma vez) e cognição (o "n de m" comunica volume e posição, algo que scroll infinito não comunica bem). Pagination responde a duas perguntas que o usuário frequentemente tem em mente: "quantos resultados existem?" e "onde estou dentro desse total?".
Quando usar:
- Conjuntos de dados grandes (a maioria dos design systems pesquisados recomenda pagination acima de 25–50 itens — Carbon cita "mais de 25 itens", Polaris cita "mais de 50")
- Contextos onde o usuário precisa voltar a uma posição específica (ex.: "eu vi esse produto na página 3")
- Contextos onde SEO importa: páginas paginadas geram URLs indexáveis, diferente de scroll infinito
- Tabelas de dados administrativos, onde o usuário compara e audita, não apenas "descobre" conteúdo
Quando não usar / preferir alternativa:
- Infinite scroll / "load more": melhor para feeds de descoberta casual (redes sociais, catálogos de exploração), onde o usuário não precisa retornar a uma posição exata e a métrica que importa é engajamento contínuo, não localização. Custo: quebra o rodapé da página, dificulta alcançar footer/rodapé, atrapalha "voltar" do navegador e prejudica SEO. O GOV.UK Design System, por exemplo, recomenda pagination em vez de scroll infinito justamente por esses problemas de usabilidade.
- Cursor/keyset pagination sem números: quando o total de itens é desconhecido, muito volátil (dados em tempo real) ou caro de contar — nesse caso só "anterior/próximo" faz sentido, sem numeração (é o modelo adotado pelo Shopify Polaris, ver seção 15).
- Filtros/facetas podem ser preferíveis a paginação profunda quando o problema real é "hard to find", não "muito conteúdo para exibir de uma vez".
Componentes alternativos e quando preferi-los: um Stepper/wizard não deve ser confundido com pagination — ambos navegam por "páginas", mas o stepper implica progresso sequencial e obrigatório (não se pula etapas livremente), enquanto pagination implica navegação livre e não sequencial por natureza.
O usuário já viu pagination centenas de vezes — em resultados de busca (Google popularizou o padrão de números + "Next"), em e-commerce, em fóruns. As expectativas são fortemente convencionalizadas:
- Números de página aparecem em ordem crescente, da esquerda para a direita (ou direita para esquerda em RTL)
- A página atual está visualmente destacada (cor, contorno, peso de fonte) e não é clicável/redundante
- Existe algum indicador de "anterior" à esquerda e "próximo" à direita
- Reticências (
...) sinalizam páginas omitidas quando o total é grande — o usuário entende isso como "há mais páginas aqui, mas não cabem" - O componente fica no rodapé do conteúdo que ele pagina, não no topo (embora paginação duplicada topo+rodapé exista em tabelas densas)
Relação com padrões nativos: não há um padrão de pagination "nativo" de sistema operacional equivalente a um date picker do iOS, mas o padrão web (Google, Amazon) é tão dominante que qualquer desvio grande — por exemplo, trocar números por apenas anterior/próximo em um contexto onde o usuário espera saltar direto para a página 8 — tem custo cognitivo real: o usuário perde a capacidade de "saltar" e precisa clicar repetidamente. É uma decisão válida (ver Polaris na seção 15), mas deve ser deliberada, não default.
Quebrar a convenção de posição da página atual (por exemplo, destacar a próxima página em vez da atual) é o tipo de desvio que gera desorientação imediata — não há ganho que justifique esse tipo de inconsistência.
| Elemento | Tipo | Função |
|---|---|---|
| Container/nav | Obrigatório | Agrupa toda a navegação; landmark de navegação para leitores de tela |
| Botão "Anterior" (Previous) | Obrigatório | Move uma página para trás; desabilitado na primeira página |
| Botão "Próximo" (Next) | Obrigatório | Move uma página para frente; desabilitado na última página |
| Itens de página numerados | Opcional | Links/botões para saltar diretamente a uma página específica |
| Indicador de página atual | Obrigatório (se houver números) | Comunica visualmente e semanticamente em qual página o usuário está |
| Reticências (ellipsis) | Auxiliar | Substitui um intervalo de páginas omitidas quando o total excede o espaço disponível |
| Botão "Primeira página" / "Última página" | Opcional | Atalho para os extremos, útil quando há muitas páginas |
| Contador "X–Y de Z itens" | Auxiliar | Comunica volume total e posição relativa (mais informativo que apenas "página 2 de 10") |
| Seletor de itens por página | Auxiliar | Permite ao usuário controlar densidade (comum em tabelas de dados — Carbon, MUI TablePagination) |
| Campo de input "ir para página" | Auxiliar | Salto direto por digitação, útil quando há dezenas/centenas de páginas (Spectrum "explicit" variant) |
Descrevendo de fora para dentro: um <nav aria-label="Pagination"> contém uma lista (<ul>/<ol>) de itens de página; cada item é um link ou botão; o item da página atual carrega aria-current="page"; os extremos (anterior/próximo) ficam fora ou nas pontas dessa lista. Elementos auxiliares (contador, seletor de itens por página) tipicamente ficam fora do <nav>, pois não são navegação em si — são metadados sobre a navegação.
| Estado | Quando ocorre | Comportamento | Representação visual |
|---|---|---|---|
| Default | Item de página clicável, não é a atual | Interativo, navega ao ser ativado | Cor neutra, sem destaque |
| Hover | Cursor sobre um item clicável | Sinaliza interatividade | Fundo/contorno sutil |
| Focus / Focus-visible | Item recebeu foco via teclado ou toque | Indica posição do foco para navegação por teclado | Anel de foco visível (focus-visible não deve aparecer em clique de mouse, só em navegação por teclado) |
| Active/pressed | Durante o clique/tap | Feedback tátil momentâneo | Leve escurecimento/depressão |
| Current/selected | Página atualmente exibida | Não interativo ou interativo-mas-redundante; comunica posição | Cor de destaque, peso de fonte maior, aria-current="page"
|
| Disabled | Botão "Anterior" na primeira página; "Próximo" na última | Não interativo, não recebe foco (ou recebe com aria-disabled) |
Opacidade reduzida, cursor not-allowed |
| Loading | Após clique, aguardando novo conjunto de dados (paginação assíncrona) | Impede novos cliques até resolver; idealmente anuncia mudança via aria-live
|
Skeleton no conteúdo paginado e/ou spinner |
| Truncated (ellipsis) | Quando o total de páginas excede o espaço de exibição | Não interativo por padrão (ou expande a lista ao ser clicado, em algumas implementações) |
… textual, não numérico |
Distinção importante — disabled vs. simplesmente ausente: desabilitar visualmente o botão "Anterior" na página 1 (em vez de escondê-lo) é preferível porque mantém a previsibilidade do layout — o usuário não precisa recalcular onde os controles estão a cada página. Já a diferença entre focus e focus-visible importa porque um clique de mouse no item "página atual" não deveria mostrar o anel de foco proeminente que a navegação por Tab mostra; misturar os dois cria ruído visual para usuários de mouse.
Mouse: clique em qualquer item numerado, em anterior/próximo, primeira/última, ou nas reticências (quando expansíveis) navega para a página correspondente. Hover mostra affordance de clicabilidade. Duplo clique não tem significado especial (deve ser idempotente — clicar duas vezes na mesma página não deve gerar efeitos colaterais).
Touch: tap equivalente ao clique. Como pagination frequentemente usa alvos pequenos (números de 1 dígito), a área de toque mínima (ver seção 6) é especialmente crítica aqui — é um dos componentes mais comumente reprovados em auditorias de alvo de toque por comprimir muitos números lado a lado.
Teclado:
| Tecla | Ação |
|---|---|
Tab |
Move o foco para o próximo elemento interativo da pagination (cada item é uma parada separada — pagination não usa roving tabindex por padrão, ao contrário de tabs) |
Shift + Tab |
Move o foco para o elemento interativo anterior |
Enter / Space
|
Ativa o item focado (navega para a página) |
Esc |
Sem função padrão (não há um estado "aberto" a fechar, exceto se o input de "ir para página" tiver um popover associado) |
Diferente de padrões como Tabs (que usam roving tabindex e setas para navegar dentro do grupo), a prática mais comum documentada — inclusive por Carbon — é que cada controle de pagination seja uma parada independente no tab order, navegada com Tab/Shift+Tab, não com setas. Isso é coerente com o fato de pagination ser semanticamente uma lista de links de navegação, não um widget composto como um radiogroup.
Quando o seletor de "itens por página" é um <select>, ele segue o padrão de teclado nativo de select (abre com Space/setas, navega com setas, confirma com Enter, fecha com Esc) — Carbon documenta isso explicitamente.
Esta é a seção com o achado mais importante da pesquisa: o WAI-ARIA Authoring Practices Guide (APG) não define um padrão de design (design pattern) dedicado a "Pagination", ao contrário de tabs, accordion, breadcrumb, dialog, etc. A prática de acessibilidade recomendada para pagination surge, em vez disso, de um consenso de mercado que combina HTML semântico nativo com o atributo aria-current — o mesmo mecanismo usado no padrão de Breadcrumb, que é documentado pelo APG.
Semântica HTML: o padrão recomendado por praticamente toda a indústria (Visa Accessibility, A11Y Style Guide, GOV.UK/MoJ Design System, dev.to, a11ymatters, entre outros) é:
- Envolver todo o componente em um elemento
<nav>comaria-labeldescritivo (ex.:aria-label="Pagination"ou, se houver múltiplas paginações na mesma tela, um rótulo único por instância, comoaria-label="Pagination for search results") - Estruturar os itens como uma lista (
<ul>/<ol>com<li>), cada um contendo um link (<a href="...">) ou botão, dependendo se a navegação muda a URL (SSR/deep-linking) ou é puramente client-side -
"A primeira regra do ARIA é não usar ARIA": prefira
<a href="?page=2">nativo em vez de<div role="link">; use<button>nativo para ações que não mudam URL
ARIA — roles, states e properties:
-
role="navigation"— implícito no elemento<nav>, redundante declará-lo explicitamente em HTML nativo -
aria-labelno<nav>— nome acessível da região de navegação (evitearia-label="Pagination navigation", que é lido de forma redundante como "Pagination navigation, navigation" por alguns leitores de tela, já que o role já anuncia "navigation") -
aria-current="page"— no item que representa a página atualmente exibida (mesmo mecanismo do padrão Breadcrumb do APG e definido na especificação ARIA dearia-current, que também lista os valoresstep,location,date,time,true) -
aria-labelem cada item individual — para que o leitor de tela anuncie "Page 3" ou "Go to page 3" em vez de apenas "3" (o texto visível "3" sozinho é ambíguo fora de contexto) -
aria-disabled="true"(em vez de remover o elemento) nos botões anterior/próximo quando não há mais páginas naquela direção — mantém o elemento no DOM e no fluxo de leitura, comunicando que a ação existe mas está indisponível, em vez de simplesmente escondê-la -
aria-setsizeearia-posinset— opcionalmente, em cada item, para comunicar "item 3 de 10" mesmo quando nem todos os números estão visíveis (usado quando há truncamento por reticências)
Navegação por teclado e gerenciamento de foco: não há focus trap nem retorno de foco obrigatório (não é um padrão de overlay). O ponto sensível é a paginação assíncrona (SPA que troca conteúdo sem recarregar a página): ao navegar, o foco deve permanecer previsível — uma prática recomendada é mover o foco para o início do novo conteúdo paginado (ou para um heading dele) após a troca, para que usuários de leitor de tela não precisem navegar de volta ao topo manualmente. Sem isso, o usuário de teclado permanece com foco no botão "Próximo" enquanto o conteúdo mudou silenciosamente.
Leitores de tela e mudanças dinâmicas: quando pagination atualiza o conteúdo via JavaScript (sem reload de página), a mudança deve ser anunciada — uma região aria-live="polite" envolvendo o contador de resultados ("Mostrando 11–20 de 87 itens") é a forma mais comum de comunicar isso sem ser intrusiva. Anunciar cada troca de página de forma abrupta (aria-live="assertive") é desnecessário e cansativo.
Outros:
- Contraste: o item da página atual deve atender ao mínimo de contraste de texto do WCAG (4.5:1 para texto normal, AA), e o estado de "atual" não pode depender só de cor — precisa também de peso de fonte, contorno ou outro sinal não-cromático (WCAG 1.4.1, uso de cor).
- Área mínima de toque: WCAG 2.2 (critério 2.5.8, Target Size Minimum) exige no mínimo 24×24px CSS por alvo, com recomendação de mercado de 44×44px. Pagination é um dos componentes mais frequentemente reprovado aqui, pois designers tendem a comprimir números de página lado a lado para caber mais.
-
Zoom/reflow: em telas estreitas ou zoom de 400%, uma lista longa de números (ex.: 1 2 3 4 5 6 7 8 9 10) deve poder quebrar linha (
flex-wrap) ou ser truncada com reticências, nunca forçar scroll horizontal do conteúdo da página inteira (WCAG 1.4.10, Reflow).
Pagination não é um componente de captura de dados no sentido formal (não há "campo obrigatório" nem submissão), então a maior parte da seção 7 do framework não se aplica diretamente. A exceção é o campo de input "ir para página" (Spectrum "explicit" variant, ou implementações com salto direto):
-
Validação: ao digitar um número fora do intervalo (ex.: "999" quando só existem 40 páginas) ou um valor não numérico, o campo deve rejeitar/corrigir no
blurou nosubmit(Enter), não a cada tecla digitada — validar a cada caractere impede o usuário de digitar "10" (pois "1" sozinho seria "inválido" se só houver, por exemplo, 8 páginas mas o usuário está prestes a digitar "1" de "18"). - Feedback: ao corrigir automaticamente para o valor mais próximo válido (ex.: grampear em 40 se o usuário digitar 999), comunique a correção — não apenas troque o valor silenciosamente.
- Erros de carregamento (ex.: falha de rede ao buscar a página seguinte) devem ser tratados na seção 13 (edge cases), não aqui, pois não são "validação" no sentido de formulário.
Boas práticas de rotulagem, com exemplos corretos e incorretos:
✅ aria-label="Ir para a página 4" em cada item numérico
❌ Nenhum aria-label, deixando o leitor de tela anunciar apenas "4, link" sem contexto
✅ Contador "Mostrando 21–30 de 214 resultados" — comunica volume real ❌ "Página 3 de 22" isoladamente, sem dizer quantos itens totais existem — obriga o usuário a fazer conta mental se quiser saber o volume
✅ Botões rotulados como "Anterior" / "Próximo" (ou ícones com aria-label equivalente)
❌ Ícones de seta sem aria-label, deixando o leitor de tela anunciar apenas "botão" sem indicar direção
✅ Rótulo do <nav>: aria-label="Pagination" (conciso, sem redundância com o role)
❌ aria-label="Navegação de paginação" (redundante — o role já diz "navegação")
Concisão: números de página não precisam de rótulos visíveis extensos (o "3" sozinho já é suficiente visualmente, desde que o contexto — estar dentro do <nav> de pagination — seja claro); é o rótulo acessível (aria-label) que precisa ser mais explícito, não o texto visível.
Tom: o vocabulário deve ser consistente ao longo do produto — se "Anterior/Próximo" é usado em pagination, o mesmo par não deve virar "Voltar/Avançar" em um stepper próximo, pois isso cria dissonância entre padrões que já são visualmente parecidos.
| Variante | Quando usar | Quando não usar / risco |
|---|---|---|
| Numerada com reticências (1 2 … 8 9 10) | Padrão para a maioria dos casos com total de páginas conhecido e navegável | Evite quando o total de páginas é muito volátil (dados mudando em tempo real) — a numeração fica instável |
| Apenas anterior/próximo (sem números) | Quando o total de itens é desconhecido, caro de calcular, ou a navegação é predominantemente sequencial (ex.: feed cronológico) | Perde a capacidade do usuário de "saltar" direto a uma página específica — trade-off deliberado, não default |
| Compacta "Página X de Y" | Espaços estreitos (mobile, componentes pequenos) | Não permite salto direto sem um input adicional |
| Com input de salto direto ("ir para página __") | Quando há dezenas/centenas de páginas e clicar item a item é impraticável | Adiciona complexidade de validação (seção 7); desnecessário se há poucas páginas |
| Com seletor de itens por página | Tabelas de dados administrativos, onde densidade é uma escolha do usuário | Menos comum em conteúdo editorial público (blog, catálogo) |
| Tamanhos (small/medium/large) | Adaptar densidade ao contexto (dentro de um card compacto vs. rodapé de página inteira) | Manter poucos tamanhos — 2–3 no máximo; mais que isso gera inconsistência sem ganho real |
| Aparências visuais (outlined, preenchida, "ghost"/texto simples) | Alinhamento com o sistema visual do produto | Múltiplas aparências para o mesmo propósito (como no MUI: default, outlined, rounded) tendem a virar escolha estética arbitrária de cada time, não uma decisão funcional — vale documentar quando usar cada uma ou reduzir para uma única aparência padrão |
Combate à proliferação: das variantes acima, numerada-com-reticências e apenas anterior/próximo são as essenciais — cobrem os dois modelos mentais reais (navegação por salto vs. navegação sequencial). As demais (tamanho, aparência, input de salto) são modificadores, não variantes funcionais novas, e podem ser tokens/props em vez de componentes distintos.
Desktop: espaço amplo permite exibir mais números de página simultaneamente (ex.: 7–9 itens visíveis antes de truncar com reticências), além de elementos auxiliares como seletor de itens por página e contador, todos na mesma linha.
Tablet: geralmente mantém o mesmo padrão do desktop com menos números visíveis antes de truncar (ex.: 5 itens), e elementos auxiliares podem quebrar para uma segunda linha.
Mobile: o padrão muda de forma mais perceptível. Opções comuns:
- Reduzir a numerados para "Página 3 de 22" com apenas anterior/próximo (elimina a necessidade de números tocáveis pequenos)
- Empilhar (
flex-wrap) os elementos auxiliares (contador, seletor de itens por página) acima ou abaixo dos controles de navegação - Aumentar a área de toque dos botões anterior/próximo (que passam a ser os controles primários) para pelo menos 44×44px
Por que a transformação ocorre: a limitação não é só espaço horizontal — é a área mínima de toque (seção 6). Em telas largas, alvos de 32px lado a lado ainda são clicáveis com precisão de mouse; em touch, a mesma densidade vira fonte de erro de toque, então a resposta correta não é "encolher mais", é "mostrar menos números e confiar em anterior/próximo + indicador textual".
Um caso à parte é o próprio Shopify Polaris (seção 15): em iOS/Android nativos, a recomendação é abandonar pagination discreta em favor de infinite scroll — um exemplo de padrão que muda de categoria, não só de layout, entre plataformas.
-
Depende de:
Button/IconButton(para anterior/próximo/primeira/última),Link(quando a navegação é baseada em URL/SSR), opcionalmenteSelect(para itens por página) eTextField(para input de salto direto). -
Compõe-se com:
Table/DataGrid(paginação de linhas),List/ResourceList(paginação de itens de lista), resultados de busca, galerias. - Classificação: composto (composite pattern) — não é um componente de base atômico como Button ou Icon, mas também não é um "pattern" de página inteira como um fluxo de checkout. É montado a partir de componentes base (Button, Link, Select) organizados em uma estrutura de navegação com regras próprias de estado (página atual, truncamento).
O lugar de Pagination na arquitetura é o de um componente de navegação secundária, ao lado de Breadcrumb e Tabs — os três compartilham o mecanismo aria-current, o que é um forte indício de que devem ser implementados com uma base compartilhada (ex.: um hook interno useCurrentIndicator ou token de estilo comum para "item atual") em vez de reimplementados de forma independente em cada componente.
Proposta de API para uma implementação React/TypeScript, coerente com os estados (seção 4) e variantes (seção 9):
type PaginationProps = {
/** Página atual, 1-indexado (mais legível para URLs e para o usuário que para 0-indexado) */
currentPage: number;
/** Total de páginas. Omitir se o total for desconhecido (ativa modo "apenas anterior/próximo") */
totalPages?: number;
/** Componente controlado: o consumidor decide o que fazer na mudança de página */
onPageChange: (page: number) => void;
/** Variante funcional: numerada (padrão) ou apenas anterior/próximo */
variant?: "numbered" | "simple";
/** Tamanho visual */
size?: "small" | "medium" | "large";
/** Quantos números de vizinhança exibir de cada lado da página atual antes de truncar */
siblingCount?: number;
/** Exibe atalhos para primeira/última página */
showFirstLastButtons?: boolean;
/** Exibe um seletor de itens por página */
showItemsPerPageSelector?: boolean;
itemsPerPageOptions?: number[];
onItemsPerPageChange?: (itemsPerPage: number) => void;
/** Rótulo acessível do <nav>; default: "Pagination" */
"aria-label"?: string;
/** Função para gerar o aria-label de cada item numérico; default: (page) => `Go to page ${page}` */
getItemAriaLabel?: (page: number) => string;
/** Se fornecido, cada item vira um <a href> em vez de <button> (navegação SSR/deep-link) */
getPageHref?: (page: number) => string;
disabled?: boolean;
loading?: boolean;
};Decisões de API comentadas:
-
Controlado, não não-controlado:
currentPageeonPageChangeobrigatórios (não hádefaultPagecom estado interno) — pagination quase sempre precisa sincronizar com URL, query de dados ou estado de tabela externo; um componente não-controlado esconderia essa sincronização e forçaria o consumidor a "ler de volta" o estado via ref ou callback, o que é pior. -
1-indexado por padrão: alinhado com o que o usuário vê ("página 1", não "página 0") e com o que aparece na URL — decisão que o próprio MUI documenta explicitamente como escolha consciente (diferente do
TablePagination, que é 0-indexado para casar com arrays JavaScript). -
getPageHrefopcional: permite que o componente renderize<a href>nativo (melhor para SEO e "abrir em nova aba") quando aplicável, mantendo<button>como padrão para navegação puramente client-side. Essa é uma decisão que impacta diretamente a seção 6 (semântica HTML). -
totalPagesopcional em vez de obrigatório: modela o caso real de "não sei quantas páginas existem" (dados infinitos/streaming) sem precisar de um componente totalmente separado — o componente internamente cai para o modovariant="simple"quandototalPagesestá ausente. -
Enums em vez de múltiplos booleans:
variantesizecomo union types em vez de props booleanas soltas (isSimple,isSmall) evita combinações inválidas e é mais fácil de documentar/autocompletar.
| Caso | Comportamento esperado |
|---|---|
| Total de itens cabe em uma única página | Não renderizar o componente (ou renderizá-lo desabilitado/oculto) — pagination de uma página só é ruído visual |
totalPages é 0 ou dados vazios |
Ocultar o componente; o estado vazio deve ser comunicado pelo conteúdo paginado (empty state), não pela pagination |
| Usuário está na página atual e clica nela de novo | Idempotente — nenhuma nova requisição/efeito colateral, apenas ignora ou reafirma o estado |
currentPage fornecido pelo consumidor é maior que totalPages (ex.: dados mudaram e a página deixou de existir) |
Redirecionar/grampear para a última página válida, nunca renderizar um estado "página 15 de 10" |
| Muitas páginas (centenas/milhares) | Truncar agressivamente com reticências; considerar oferecer o input de salto direto (seção 9) em vez de listar dezenas de números |
| Poucas páginas (2–3) | Não usar reticências nem esconder números — exibir todos, sem necessidade de truncamento |
disabled (componente inteiro) + loading simultâneos |
disabled deve prevalecer visualmente sobre loading isolado — evitar dois indicadores de "não interaja" competindo (spinner + opacidade reduzida ao mesmo tempo é redundante) |
| Falha ao carregar a próxima página (erro de rede) | Reverter visualmente para a página anterior (não deixar a UI "presa" na página que falhou) e comunicar o erro perto do conteúdo, não apenas via toast que desaparece |
| Item de conteúdo excluído deixa a página atual vazia (ex.: usuário estava na última página, apagou o único item dela) | Recalcular totalPages e mover automaticamente para a nova última página válida |
| Paginação dentro de um container com scroll interno (não a página inteira) | Foco pós-navegação deve ir para o topo do container interno, não para o topo da página do navegador |
Faça:
- Use
aria-current="page"no item ativo e umaria-labeldescritivo em cada item ("Ir para a página 4", não apenas "4") - Desabilite (não esconda) anterior/próximo nos extremos, para manter o layout previsível
- Combine números de página com um contador textual ("21–30 de 214") quando o volume total for uma informação relevante para a tarefa do usuário
- Garanta pelo menos 24×24px de área de toque por item (idealmente 44×44px) mesmo quando os números visuais são pequenos
- Torne o componente controlado e sincronizado com a URL sempre que a navegação representa conteúdo "linkável" (blog, catálogo, resultados de busca)
Evite:
- Truncar sem reticências (ex.: pular de "3" direto para "8" sem nenhum sinal visual de que há páginas omitidas)
- Depender só de cor para indicar a página atual — adicione peso de fonte, contorno ou ícone
- Colocar pagination apenas no topo do conteúdo sem repeti-la no rodapé em listas longas (o usuário que rolou até o fim não deveria precisar rolar de volta ao topo para navegar)
Não faça:
- Não use
aria-selectedem itens de pagination (é um erro documentado por a11ymatters.com) —aria-selectedpertence a padrões derole="option"/tablist, não a links de navegação; o atributo correto éaria-current="page" - Não implemente pagination sem
<nav>/landmark — sem isso, leitores de tela não oferecem ao usuário o atalho de "pular para a navegação" que muitos usam para localizar o componente rapidamente - Não misture pagination com scroll infinito no mesmo contexto (ex.: números de página que, ao mesmo tempo, carregam mais itens automaticamente ao rolar) — isso quebra as duas convenções mentais ao mesmo tempo e confunde tanto quem espera "clicar para ir" quanto quem espera "rolar para carregar"
| Sistema | Documenta oficialmente? | Anatomia/variantes | Acessibilidade documentada | Observação |
|---|---|---|---|---|
| Material Design (M3) |
Não. O m3.material.io (Google, oficial) não lista Pagination entre seus componentes. O que existe amplamente sob "Material" é o Material UI (MUI), uma implementação de terceiros/comunidade — não a especificação oficial do Google. |
(via MUI) Basic, outlined, rounded; tamanhos small/medium/large; showFirstButton/showLastButton; integração dedicada TablePagination para tabelas |
(via MUI) role="navigation", aria-label="pagination navigation" por padrão, aria-label individual por item ("go to page 1", etc.) |
Achado relevante: a ausência de um componente oficial de Pagination no M3 é uma lacuna real da especificação — equipes que seguem Material puro precisam desenhar do zero ou adotar MUI como referência de fato |
| Carbon (IBM) | Sim, com página dedicada de uso e de acessibilidade | Seletor de itens por página + "X–Y de Z itens" + "de N páginas", com setas anterior/próximo; recomenda uso acima de ~25 itens | Documentação de acessibilidade própria e explícita: nomes acessíveis programáticos "Page", "Previous", "Next"; navegação de teclado detalhada (Space/setas abrem selects, Esc fecha) | O sistema mais explícito sobre quando usar (limiar numérico) e sobre teclado — referência forte para a seção 5 e 6 deste documento |
| Atlassian Design System | Sim, componente listado (Pagination), mas a página pública de guidelines é enxuta (pouco texto de uso além da descrição de uma linha); a implementação detalhada vive no Atlaskit (biblioteca de componentes) |
Documentado como parte da categoria "Navigation", ao lado de Breadcrumbs e Tabs | Não há página de acessibilidade dedicada publicamente detalhada como a de Carbon | Reforça a classificação da seção 11: Atlassian agrupa Pagination, Breadcrumbs e Tabs na mesma categoria de navegação, validando que compartilham fundamentos (aria-current) |
| Shopify Polaris | Sim, com guidelines de uso explícitas |
Somente anterior/próximo — sem números de página. Web usa botões; iOS/Android usam infinite scroll em vez de pagination discreta. Prop type: "table" | "page" para variar contexto de uso |
accessibilityLabel e accessibilityLabels dedicados nas props; recomenda desabilitar o botão correspondente no primeiro/último item |
O sistema mais divergente do "padrão de mercado": deliberadamente sem salto direto a uma página numerada, e com comportamento diferente por plataforma — ilustra bem o trade-off da seção 9 |
| Adobe Spectrum | Parcialmente. Existe em Spectrum CSS (duas variantes: "explicit", com campo de texto + contador "of N pages"; e "listing", com números tradicionais), mas não está disponível publicamente no React Spectrum (a implementação React oficial) — há uma discussão pública da própria equipe Adobe confirmando que o componente ficou represado após um redesenho e não foi liberado | Variante "explicit" (input numérico + "of N pages" + prev/next) é praticamente única entre os 6 sistemas pesquisados — nenhum outro oferece esse padrão como variante de primeira classe | Ícones prev/next com aria-label/aria-hidden na versão CSS |
Achado relevante: mesmo dentro de um único fornecedor (Adobe), a camada de design tokens (CSS) está à frente da camada de implementação React oficial — um lembrete de que "documentado" não é sinônimo de "disponível na biblioteca" |
| Fluent UI 2 (Microsoft) |
Não. Não há um componente "Pagination" na lista oficial de componentes web/React do Fluent 2. Há uma feature request pública aberta desde 2016 (ainda sem componente oficial) e uma discussão recente (2024) de usuários pedindo a criação do componente para uso com DataGrid/DetailsList, com sugestões de comunidade sobre como estruturá-lo |
— (não aplicável, componente inexistente) | — (não aplicável) | Mesmo achado estrutural que Material: dos 6 sistemas, dois (Material oficial e Fluent) não têm Pagination como componente de primeira classe — é o componente com a lacuna de cobertura mais consistente entre grandes design systems |
Síntese — diferenças, semelhanças e aprendizados:
- Lacuna recorrente: Material Design (oficial) e Fluent UI 2 simplesmente não têm um componente de Pagination documentado — ambos deixam a decisão para consumidores ou para bibliotecas de terceiros (MUI, no caso de Material). Isso é incomum para um componente tão fundamental e sugere que grandes design systems tratam Pagination como "problema de aplicação", não de sistema — possivelmente porque o padrão varia tanto por contexto (blog vs. tabela administrativa vs. resultado de busca) que um componente único parece pouco reutilizável aos olhos desses times.
-
Convergência em acessibilidade: todos os sistemas que documentam o componente (Carbon, Polaris, MUI) convergem no mesmo mecanismo básico —
<nav>/landmark +aria-labelno container +aria-labelpor item + desabilitar (não remover) os extremos. Isso reforça que, mesmo sem um padrão formal do WAI-ARIA APG, a indústria já convergiu para uma prática de fato. - Divergência real de UX, não só visual: Polaris é o único a abandonar números de página deliberadamente (a favor de anterior/próximo, e de infinite scroll em mobile), o que é uma decisão de produto (Shopify prioriza fluxo contínuo de "merchant" sobre a precisão de "salto para página X") mais do que uma diferença estética.
- Camada de design tokens à frente da implementação: o caso Spectrum (documentado em CSS, ausente em React) mostra que a existência de um componente na documentação de design não garante disponibilidade na biblioteca de código — um risco a evitar no design system próprio, mantendo documentação e implementação sincronizadas desde o início.
Fontes consultadas nesta seção: ver seção de Referências ao final.
UX:
- Adote numerada com reticências como padrão para conteúdo linkável/indexável (blogs, catálogos, resultados de busca) e apenas anterior/próximo para tabelas de dados internas de alta frequência de atualização, onde o total pode mudar a cada segundo.
- Sempre acompanhe os números com um contador textual de volume ("X–Y de Z") — é a informação que mais reduz a carga cognitiva de "onde estou" e é barata de implementar.
- Trate scroll infinito como um componente separado (não uma variante de Pagination), já que os dois têm modelos de interação, SEO e acessibilidade fundamentalmente diferentes — misturar as duas responsabilidades em um único componente tende a gerar props condicionais confusas.
Acessibilidade:
- Padronize institucionalmente no mecanismo
<nav aria-label> + aria-current="page" + aria-label por item + aria-disabled nos extremos, já que não existe uma referência formal do APG a seguir — documente essa decisão explicitamente no design system, citando a ausência de padrão oficial como justificativa (para que futuros mantenedores não presumam que existe um padrão ARIA "certo" e passem a improvisar de formas diferentes). - Trate a área mínima de toque (24×24px, idealmente 44×44px) como requisito de design token, não como responsabilidade caso a caso de cada implementação — é o ponto de falha mais comum do componente.
- Garanta anúncio via
aria-live="polite"do contador de resultados em navegações assíncronas, e mova o foco para o início do conteúdo atualizado.
Arquitetura:
- Reconheça Pagination como componente composto (Button + Link + Select) e compartilhe a lógica de "item atual" (
aria-current) com Breadcrumb e Tabs, que resolvem o mesmo problema semântico de "onde estou" em contextos diferentes. - Separe a variante
numbereddasimple(anterior/próximo) na API desde o início, em vez de tentar um único componente universal com muitas props condicionais — os dois padrões atendem modelos mentais e casos de uso genuinamente diferentes (ver seção 9).
Implementação:
- Componente controlado, 1-indexado, com suporte opcional a
hrefpara casos SSR/deep-linking — replicando a decisão mais bem documentada entre os sistemas pesquisados (MUI). - Garanta que a implementação de código não fique atrás da documentação de design tokens — o caso Spectrum CSS vs. React Spectrum é um alerta direto sobre esse risco.
Documentação:
- Como não há uma referência formal do APG, documente explicitamente as fontes de decisão de acessibilidade (este documento, os guidelines de Carbon/Polaris/MUI) para que a equipe entenda que a prática é um consenso de mercado, não uma norma fixa — e para que revisões futuras do padrão web sejam monitoradas (o feed de discussão pública do W3C ARIA Working Group é o lugar de acompanhar caso um padrão formal de Pagination seja proposto).
- Documente o limiar recomendado para ativar pagination (ex.: "usar acima de 25–50 itens", seguindo o precedente de Carbon e Polaris) como parte das guidelines de uso, não deixando a decisão de "quando paginar" a critério individual de cada time de produto.
WAI-ARIA / acessibilidade:
- WAI-ARIA Authoring Practices Guide — Índice de padrões (confirma ausência de padrão dedicado de Pagination): https://www.w3.org/WAI/ARIA/apg/patterns/
- WAI-ARIA APG — Breadcrumb Pattern (mecanismo
aria-currentde referência): https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/ - MDN —
aria-current: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current - Visa Accessibility Guidelines — Pagination (NAV-007): https://developer.visa.com/pages/accessibility/navigation/nav-007
- A11Y Style Guide — Navigation/Pagination: https://a11y-style-guide.com/style-guide/section-navigation.html
- Accessible User Interface Guidelines (AUIG) — Pagination: https://auig.org/pages/pagination.html
- a11ymatters.com — Pagination pattern: https://a11ymatters.com/pattern/pagination/
- tempertemper.net — An accessible pagination pattern (or two): https://www.tempertemper.net/blog/an-accessible-pagination-pattern-or-two
- dev.to — Accessible components: Pagination: https://dev.to/micaavigliano/accessible-components-pagination-58c2
Design systems (comparativo, seção 15):
- Material UI (implementação de referência da comunidade para Material) — React Pagination: https://mui.com/material-ui/react-pagination/
- Material UI — Pagination API: https://mui.com/material-ui/api/pagination/
- Carbon Design System — Pagination (uso): https://v10.carbondesignsystem.com/components/pagination/usage/ e https://carbondesignsystem.com/components/pagination/code/
- Carbon Design System — Pagination (acessibilidade): https://carbondesignsystem.com/components/pagination/accessibility/
- Atlassian Design — Pagination: https://atlassian.design/components/pagination
- Atlaskit — Pagination package: https://atlaskit.atlassian.com/packages/design-system/pagination
- Shopify Polaris — Pagination: https://polaris.shopify.com/components/pagination
- Adobe Spectrum CSS — Pagination (listing): https://opensource.adobe.com/spectrum-css-vr-results/vrt-3b7f632-2021-04-15_04-28-00/dist/docs/pagination-listing.html
- Adobe Spectrum CSS — Pagination (explicit): https://opensource.adobe.com/spectrum-css/pagination-explicit.html
- GitHub — discussão sobre
@react-spectrum/paginationnão publicado publicamente: https://github.com/adobe/react-spectrum/discussions/1712 - Fluent 2 Design System — visão geral de componentes React (Pagination ausente): https://fluent2.microsoft.design/components/web/react/
- GitHub — solicitação de novo componente Pagination no Fluent UI (2016, ainda aberta): https://github.com/microsoft/fluentui/issues/349
- GitHub — discussão da comunidade sobre criação de componente Pagination no Fluent UI (2024): https://github.com/microsoft/fluentui/discussions/32967
Referências adicionais:
- GOV.UK / Ministry of Justice Design System — Pagination (referência de padrão governamental maduro, usado como apoio na seção 1 e 16): https://design-patterns.service.justice.gov.uk/components/pagination