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.
<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).
<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.
<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.
<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.
<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
| Prop | Descripción |
|---|---|
bind:page? number Por defecto 1 | Pá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 1 | Páginas que se muestran a cada lado de la actual. |
boundaries? number Por defecto 1 | Pá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 llevaaria-current="page". Se ve con el fondo deselected, 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-disabledy 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:
smsiempre, ymden 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}ysize="sm"en el móvil.