Ir al contenido

Componentes · Ola 2 · Navegación

Pagination

Moverse entre las páginas de un listado largo: anterior, siguiente y cada número. Los números van en mono con cifras tabulares, la página actual con la luz de la selección y, si hay muchas, las demás se resumen con elipsis.

import { Pagination } from '@archeblack/ui';

Ejemplos

Paginación

bind:page y pageCount. Con 12 páginas se ven la primera, la actual con sus vecinas y la última.

Svelte
<script lang="ts">
	import { Pagination } from '@archeblack/ui';

	let page = $state(2);
</script>

<Pagination bind:page pageCount={12} />

Tallas

sm (32 px) y md (36 px, por defecto; 32 px en pantallas angostas).

Svelte
<script lang="ts">
	import { Pagination } from '@archeblack/ui';

	let small = $state(5);
	let medium = $state(5);
</script>

<div style="display: grid; justify-items: center; gap: var(--arche-spacing-4)">
	<Pagination bind:page={small} pageCount={20} size="sm" aria-label="Paginación (sm)" />
	<Pagination bind:page={medium} pageCount={20} aria-label="Paginación (md)" />
</div>

Al cambiar de página

onPageChange llega con la página nueva, después de actualizar page. Un role='status' anuncia el rango de resultados.

Svelte
<script lang="ts">
	import { Pagination } from '@archeblack/ui';

	const perPage = 25;
	const total = 1204;
	let page = $state(1);
	let status = $state('');

	// onPageChange llega después de actualizar page: aquí se piden los resultados de la página nueva.
	function load(next: number) {
		const from = (next - 1) * perPage + 1;
		const to = Math.min(next * perPage, total);
		status = `Resultados ${from}–${to} de ${total}`;
	}
</script>

<div style="display: grid; justify-items: center; gap: var(--arche-spacing-3)">
	<Pagination
		bind:page
		pageCount={Math.ceil(total / perPage)}
		onPageChange={load}
		aria-label="Páginas de resultados"
	/>
	<p role="status" style="margin: 0; color: var(--arche-color-text-muted)">{status}</p>
</div>

Con links

Con href, cada página es un link. La página actual sale de la URL.

Svelte
<script lang="ts">
	import { page } from '$app/state';
	import { Pagination } from '@archeblack/ui';

	// La página vive en la URL (?page=3): la paginación la recibe y no la cambia.
	const current = $derived(Number(page.url.searchParams.get('page')) || 3);
</script>

<!--
	Con href, cada página es un link: se abre en otra pestaña y los buscadores la siguen.
	data-sveltekit-noscroll deja la página en su lugar al navegar.
-->
<div data-sveltekit-noscroll>
	<Pagination page={current} pageCount={9} href={(n) => `?page=${n}`} size="sm" />
</div>

Compacta

Con siblings en 0: la actual, los extremos y las elipsis. Entra en una pantalla de 320 px.

Svelte
<script lang="ts">
	import { Pagination } from '@archeblack/ui';

	let page = $state(10);
</script>

<!-- Sin vecinas: la actual, los extremos y dos elipsis. Entra en un teléfono de 320 px. -->
<Pagination bind:page pageCount={40} siblings={0} size="sm" />

Props

Props de Pagination
PropDescripción
bind:page? number Por defecto 1Página actual, desde 1. Si está fuera de rango, se marca la más cercana (sin cambiar page).
pageCount number Cantidad de páginas.
siblings? number Por defecto 1Páginas que se muestran a cada lado de la actual.
boundaries? number Por defecto 1Páginas que se muestran siempre al principio y al final.
size? 'sm' | 'md' Por defecto 'md'sm: botones de 32 px con cifras de 13 px. md: botones de 36 px con cifras de 15 px, que en pantallas de hasta 30 rem bajan a 32 px para que la fila entre en un teléfono.
onPageChange? (page: number) => void Se llama con la página nueva al elegir otra con un botón, después de actualizar page y solo si el cambio se aceptó. Con href no se llama. No es onchange: ese es el evento nativo y llega al <nav> con ...rest.
href? (page: number) => string Con href, cada página es un link a href(page). La paginación no cambia page: el producto la lee de la URL.
previousLabel? string Por defecto 'Página anterior'Nombre accesible del botón de la página anterior.
nextLabel? string Por defecto 'Página siguiente'Nombre accesible del botón de la página siguiente.
pageLabel? (page: number) => string Por defecto (page) => `Página ${page}`Nombre accesible de cada número. Tiene que incluir el número, para que la persona que usa control por voz pueda decir lo que ve.
aria-label? string Por defecto 'Paginación'Nombre del <nav>. Si hay dos paginaciones en la página, cada una con un nombre distinto.
class? ClassValue Clases del producto; se suman a arche-pagination.
...rest HTMLAttributes<HTMLElement> Cualquier otro atributo va al <nav> raíz.

Accesibilidad

  • Es un <nav> con nombre («Paginación» por defecto) y una lista: el lector anuncia la región y cuántas páginas se ven.
  • Cada número se nombra «Página 3» (pageLabel) y la actual lleva aria-current="page". Se ve con el fondo de selected, no solo con el color del texto.
  • Anterior y siguiente son botones de solo ícono con nombre («Página anterior», «Página siguiente»); el chevron es decorativo y en un idioma de derecha a izquierda se invierte.
  • En la primera y la última página, anterior y siguiente quedan con aria-disabled y sin acción, pero enfocables: si el foco estaba en «siguiente» al llegar al final, no se pierde. Al elegir un número, el mismo botón queda enfocado.
  • Las elipsis son decorativas (aria-hidden) y no cuentan como elementos de la lista.
  • Todos los botones miden 32 px como mínimo: sm siempre, y md en pantallas de hasta 30 rem (36 px en las demás).
  • Cambiar de página no mueve el foco. Si el listado de arriba cambia, anuncia el resultado con un role="status" (como en «Al cambiar de página») o lleva el foco al título del listado.

Qué evitar

  • Paginación para listas cortas que entran en una o dos pantallas. Mostrar todo, o un botón «Cargar más» si la lista crece de a poco.
  • Botones para páginas que deberían tener su propia URL (un blog, resultados de búsqueda). href: cada página es un link que se puede compartir y abrir en otra pestaña.
  • Esconder anterior y siguiente en la primera o la última página. Dejarlos deshabilitados: la fila no salta y se entiende dónde se está.
  • Muchos números en una pantalla chica que parten la fila en dos. siblings={0} y size="sm" en el móvil.