ATOM
Components (registry)Navigation

Pagination

Pages of results with prev/next and numbered jumps. Large tables and search hits.

Preview

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Table pages:

```tsx import { Pagination, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis, } from '@/components/atoms/Pagination';

<Pagination> <PaginationItem><PaginationPrevious href="?p=1" disabled /></PaginationItem> <PaginationItem><PaginationLink href="?p=1" isActive>1</PaginationLink></PaginationItem> <PaginationItem><PaginationEllipsis /></PaginationItem> <PaginationItem><PaginationNext href="?p=2" /></PaginationItem> </Pagination> ```

## Accesibilidad

- Root `nav` has `aria-label="Pagination"`; only one `isActive` link should set `aria-current="page"`. - Disable Previous on page 1 and Next on the last page.

## Cuándo no usar

- Infinite scroll feeds — use a load-more pattern, not page numbers alone. - In-panel content switching without URLs → `Tabs`.

## Criterio de uso

- Úsala cuando el usuario necesita volver a una página concreta de resultados, especialmente en tablas y búsquedas. - Deshabilita Previous en la primera página y Next en la última; no dejes enlaces que prometen una navegación inexistente. - Mantén un único enlace activo y comunica el rango o total cuando ayude a entender la posición actual.

## Gotchas

- El contenedor debe ser un `nav` con `aria-label="Pagination"` y el estado activo debe expresarse como `aria-current="page"`. - Para feeds exploratorios donde la posición exacta no importa, usa load-more o infinite scroll; no fuerces paginación numérica.

Uso

import {
  Pagination, PaginationItem, PaginationLink,
  PaginationPrevious, PaginationNext, PaginationEllipsis,
} from '@/components/atoms/Pagination';

<Pagination>
  <PaginationItem><PaginationPrevious href="?p=1" disabled /></PaginationItem>
  <PaginationItem><PaginationLink href="?p=1" isActive>1</PaginationLink></PaginationItem>
  <PaginationItem><PaginationEllipsis /></PaginationItem>
  <PaginationItem><PaginationNext href="?p=2" /></PaginationItem>
</Pagination>

Props

  • isActive: boolean
  • disabled: boolean
  • children: ReactNode (required)

Gotchas

  • a11y

    Root nav has aria-label=Pagination; only one PaginationLink should set isActive (aria-current=page).

  • react

    Compose Pagination > PaginationItem > PaginationLink|Previous|Next|Ellipsis. isActive/disabled live on link-level pieces, not the root nav.

Anatomía CSS

<div class="pagination">
  <span class="pagination__ellipsis"></span>
  <span class="pagination__item"></span>
  <span class="pagination__link"></span>
  <span class="pagination__link--active"></span>
  <span class="pagination__link--disabled"></span>
  <span class="pagination__link-icon"></span>
  <span class="pagination__list"></span>
</div>
ClasePropósito
paginationroot
pagination__ellipsiselement
pagination__itemelement
pagination__linkelement
pagination__link--activeelement
pagination__link--disabledelement
pagination__link-iconelement
pagination__listelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allbg#fafafabackground
allfg#525252muted.foreground
allborder#e5e5e5border
allhover-bg#f5f5f5accent
allhover-fg#171717accent.foreground

Animaciones

PropiedadDuraciónEasing
background-colorvar(--duration-100)var(--easing-in-out)
border-colorvar(--duration-100)var(--easing-in-out)

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* -------------------------------------------------------------------------
   Pagination

   Page navigation: prev, page links, ellipsis, next.
   Semantic <nav> + <ul> structure.

   Parts: .pagination, .pagination__list, .pagination__item,
          .pagination__link, .pagination__ellipsis
   ------------------------------------------------------------------------- */

/* ---- Nav ---- */

.pagination {
  display: flex;
  justify-content: center;
  font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
}

/* ---- List ---- */

.pagination__list {
  display: flex;
  align-items: center;
  gap: 4px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ---- Link ---- */

.pagination__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: #0a0a0a;
  font-family: inherit;
  font-size: 12.8px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color 100ms cubic-bezier(0.4, 0, 0.2, 1),
    border-color 100ms cubic-bezier(0.4, 0, 0.2, 1);
}

.pagination__link:hover {
  background-color: #f5f5f5;
  color: #171717;
}

.pagination__link:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: -2px;
}

/* Active page */
.pagination__link--active {
  border-color: #e5e5e5;
  background-color: #fafafa;
  font-weight: 600;
}

/* Disabled (prev/next at bounds) */
.pagination__link--disabled {
  color: #525252;
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ---- Nav icon ---- */

.pagination__link-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
}

.pagination__link-icon svg {
  width: 100%;
  height: 100%;
}

/* ---- Ellipsis ---- */

.pagination__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  color: #525252;
  font-size: 12.8px;
  letter-spacing: 0.04em;
}

/* ---- Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .pagination__link {
    transition-duration: 0ms;
  }
}

Codigo fuente

components-react
components/atoms/Pagination.tsx
import { type ReactNode, type AnchorHTMLAttributes } from 'react';

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

const ChevronLeft = () => (
  <svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M15 18l-6-6 6-6" />
  </svg>
);

const ChevronRight = () => (
  <svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M9 18l6-6-6-6" />
  </svg>
);

/* ---- Pagination ---- */

export function Pagination({ children, className }: { children: ReactNode; className?: string }) {
  return (
    <nav aria-label="Pagination" className={cn('pagination', className)}>
      <ul className="pagination__list">{children}</ul>
    </nav>
  );
}

/* ---- Item ---- */

export function PaginationItem({ children, className }: { children: ReactNode; className?: string }) {
  return <li className={className}>{children}</li>;
}

/* ---- Link ---- */

export type PaginationLinkProps = {
  isActive?: boolean;
  disabled?: boolean;
  children: ReactNode;
  className?: string;
} & AnchorHTMLAttributes<HTMLAnchorElement>;

export function PaginationLink({ isActive, disabled, children, className, ...props }: PaginationLinkProps) {
  return (
    <a
      aria-current={isActive ? 'page' : undefined}
      className={cn(
        'pagination__link',
        isActive && 'pagination__link--active',
        disabled && 'pagination__link--disabled',
        className,
      )}
      {...props}
    >
      {children}
    </a>
  );
}

/* ---- Previous ---- */

export type PaginationPreviousProps = {
  disabled?: boolean;
  text?: string;
  className?: string;
} & AnchorHTMLAttributes<HTMLAnchorElement>;

export function PaginationPrevious({ disabled, text = 'Previous', className, ...props }: PaginationPreviousProps) {
  return (
    <PaginationLink disabled={disabled} className={className} {...props}>
      <span className="pagination__link-icon"><ChevronLeft /></span>
      {text}
    </PaginationLink>
  );
}

/* ---- Next ---- */

export type PaginationNextProps = {
  disabled?: boolean;
  text?: string;
  className?: string;
} & AnchorHTMLAttributes<HTMLAnchorElement>;

export function PaginationNext({ disabled, text = 'Next', className, ...props }: PaginationNextProps) {
  return (
    <PaginationLink disabled={disabled} className={className} {...props}>
      {text}
      <span className="pagination__link-icon"><ChevronRight /></span>
    </PaginationLink>
  );
}

/* ---- Ellipsis ---- */

export function PaginationEllipsis({ className }: { className?: string }) {
  return <span className={cn('pagination__ellipsis', className)} aria-hidden="true">...</span>;
}

Webflow

Webflowpagination

Pega en el Designer como application/json, luego convierte a Component (Atom / Pagination) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • :focus-visible on .pagination__link:focus-visiblepseudo-class not a safe Designer variant — moved to head Custom Code
  • selector on .pagination__link-icon svgcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .pagination__link:focus-visiblecompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • @media on (prefers-reduced-motion: reduce)not a Designer breakpoint — moved to head Custom Code block
  • --focus-ring-color on :roottoken not found in tokens-nested.json — resolve upstream or the declaration stays invalid on paste
  • --focus-ring-width on :roottoken not found in tokens-nested.json — resolve upstream or the declaration stays invalid on paste

Tras pegar: Create component → nombre Atom / Pagination → Publish.

Componentes relacionados

On this page

Detalles

Publicado14 de mayo de 2026
Categorianavigation
Lectura...
Visitas...
Ayuda?Slack

Componente

Pagination

Source

components-react / cssDisponible via MCP: atom_uikit_source("pagination")
Abrir en Storybook