Skip to content

pesquisa componente pagination

Mateus Villain edited this page Aug 22, 2026 · 1 revision

Pesquisa de 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.


1. Definição e propósito

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.


2. Modelo mental do usuário

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.


3. Anatomia

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.


4. Estados

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.


5. Interações

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.


6. Acessibilidade

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> com aria-label descritivo (ex.: aria-label="Pagination" ou, se houver múltiplas paginações na mesma tela, um rótulo único por instância, como aria-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-label no <nav> — nome acessível da região de navegação (evite aria-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 de aria-current, que também lista os valores step, location, date, time, true)
  • aria-label em 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-setsize e aria-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).

7. Validação e feedback

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 blur ou no submit (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.

8. Conteúdo e UX Writing

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.


9. Variantes

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.


10. Responsividade

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.


11. Dependências e composição

  • Depende de: Button/IconButton (para anterior/próximo/primeira/última), Link (quando a navegação é baseada em URL/SSR), opcionalmente Select (para itens por página) e TextField (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.


12. API e implementação

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: currentPage e onPageChange obrigatórios (não há defaultPage com 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).
  • getPageHref opcional: 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).
  • totalPages opcional 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 modo variant="simple" quando totalPages está ausente.
  • Enums em vez de múltiplos booleans: variant e size como union types em vez de props booleanas soltas (isSimple, isSmall) evita combinações inválidas e é mais fácil de documentar/autocompletar.

13. Casos extremos e edge cases

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

14. Boas práticas

Faça:

  • Use aria-current="page" no item ativo e um aria-label descritivo 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-selected em itens de pagination (é um erro documentado por a11ymatters.com) — aria-selected pertence a padrões de role="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"

15. Análise comparativa de mercado

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:

  1. 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.
  2. Convergência em acessibilidade: todos os sistemas que documentam o componente (Carbon, Polaris, MUI) convergem no mesmo mecanismo básico — <nav>/landmark + aria-label no container + aria-label por 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.
  3. 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.
  4. 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.


16. Recomendações para Design Systems

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 numbered da simple (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 href para 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.

Referências

WAI-ARIA / acessibilidade:

Design systems (comparativo, seção 15):

Referências adicionais: