# Componenti accessibili: Paginazione

> Guida passo passo per creare una paginazione utilizzabile da tastiera e pronta per gli screen reader, con ARIA corretta, etichette chiare ed esempi in Next.js.

*By Micaela Avigliano — Frontend & Accessibility Engineer · Pubblicato 9 ago 2025*

[Read the original on micaavigliano.com](https://micaavigliano.com/it/blog/componenti-accessibili-paginazione)

---

Oggi vedremo come creare una paginazione da zero e renderla accessibile e riutilizzabile. Spero vi sia utile!  

**Github**: [https://github.com/micaavigliano/accessible-pagination](https://github.com/micaavigliano/accessible-pagination)  
**Progetto**: [https://accessible-pagination.netlify.app/](https://accessible-pagination.netlify.app/)  

#### Custom hook per richiedere dati

## Blocco di codice

```
const useFetch = <T,>(
  url: string,
  currentPage: number = 0,
  pageSize: number = 20) => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<boolean>(false);
  useEffect(() => {
    const fetchData = async() => {
      setLoading(true);
      setError(false);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('network response failed')
        }
        const result: T = await response.json() as T;
        setData(result)
      } catch (error) {
        setError(true)
      } finally {
        setLoading(false);
      }
    };
    fetchData()
  }, [url, currentPage, pageSize]);
  return {
    data,
    loading,
    error,
  }
};
```

1.  Genereremo un hook personalizzato con un **generic type**. Questo ci permetterà di specificare il tipo di dato atteso quando si usa questo hook.
2.  Prevedremo 3 parametri. Uno per l’URL da cui eseguire il fetch dei dati, **currentPage** che è la pagina in cui ci troviamo e per impostazione predefinita è **0**, e **pageSize** che è il numero di elementi per pagina e per impostazione predefinita è **20** (potete modificare questo valore).
3.  Nel nostro stato `const [data, setData] = useState<T | null>(null);` passiamo il **generic type T**, poiché, man mano che useremo l’hook per richieste di dati diverse, ci aspetteremo tipi di dato differenti.

#### Paginazione

Per rendere accessibile una paginazione, dobbiamo tenere conto dei seguenti punti:

-   Il focus deve potersi spostare su tutti gli elementi interattivi della paginazione e avere un indicatore visibile. Per garantire una buona interazione con i lettori di schermo, è necessario usare correttamente regioni, proprietà e stati.
-   La paginazione deve essere raggruppata all’interno di un tag `<nav>` e contenere un `aria-label` che la identifichi come una paginazione.
-   Ogni elemento all’interno della paginazione deve includere `aria-setsize` e `aria-posinset`. Ora, a cosa servono? `aria-setsize` serve per indicare il numero totale di elementi all’interno dell’elenco della paginazione. Il lettore di schermo lo annuncerà nel modo seguente:
    
    screenshot dell'annuncio del voiceover: elenco di 1859 elementi
    
      
    `aria-posinset` serve per indicare la posizione dell’elemento all’interno del totale degli elementi della paginazione. Il lettore di schermo lo annuncerà nel modo seguente:
    
    screenshot del voiceover che annuncia: vai alla pagina 1. Pagina corrente, pulsante, posizione 1 di 1859
    
      
    
-   Ogni elemento della paginazione deve avere un `aria-label` per identificare a quale pagina si andrà premendo quel pulsante.
-   Prevedere pulsanti per andare alla pagina successiva/precedente e ciascuno di questi pulsanti deve avere il proprio `aria-label`.
-   Se la paginazione contiene un’ellissi (…): deve essere correttamente etichettata con un `aria-label`.
-   Ogni volta che si passa a una nuova pagina, il lettore di schermo deve annunciare in quale pagina ci si trova e quanti nuovi elementi sono presenti, nel modo seguente:
    
    voiceover dello screen reader che annuncia: pagina 3 caricata. Mostrando 20 elementi
    

Per ottenere questo risultato, lo implementeremo nel modo seguente:

## Blocco di codice

```
const [statusMessage, setStatusMessage] = useState<string>("");
useEffect(() => {
  window.scrollTo({ top: 0, behavior: 'smooth' });
  if (!loading) {
    setStatusMessage(`Page ${currentPage} loaded. Displaying ${data?.near_earth_objects.length || 0} items.`);
  }
}, [currentPage, loading]);
```

Quando la pagina avrà terminato di caricarsi, imposteremo un nuovo messaggio con la nostra `currentPage` e la lunghezza del nuovo array che stiamo caricando.  

Ora sì! Passiamo a vedere com’è strutturato il codice nel file `pagination.tsx.`

## Blocco di codice

```
interface PaginationProps {
  currentPage: number;
  totalPages: number;
  nextPage: () => void;
  prevPage: () => void;
  goToPage: (page: number) => void;
}
```

-   **currentPage** si riferirà alla pagina corrente. La gestiremo nel componente in cui desideriamo utilizzare la paginazione nel modo seguente: `const [currentPage, setCurrentPage] = useState<number>(1);`
-   **totalPages** si riferisce al totale di elementi da mostrare fornito dall’API.
-   **nextPage**: questa funzione ci permetterà di passare alla pagina successiva e aggiornare lo stato currentPage nel modo seguente:

## Blocco di codice

```
const handlePageChange = (newPage: number) => {
    setCurrentPage(newPage); 
  };
const nextPage = () => {
    if (currentPage < totalPages) {
      handlePageChange(currentPage + 1);
    }
};
```

-   **prevPage**: questa funzione ci permetterà di tornare alla pagina precedente rispetto a quella corrente e aggiornare lo stato `currentPage`.

## Blocco di codice

```
const prevPage = () => {
  if (currentPage > 1) {
    handlePageChange(currentPage - 1);
  }
};
```

-   **goToPage**: questa funzione richiederà un parametro numerico ed è la funzione che ogni elemento avrà per poter andare alla pagina desiderata. La faremo funzionare nel modo seguente:

## Blocco di codice

```
const handlePageChange = (newPage: number) => {
    setCurrentPage(newPage); 
};
```

Per dare vita alla nostra paginazione manca un ultimo passo: creare l’array che itereremo nel nostro elenco! Per farlo dobbiamo seguire i seguenti passaggi:

1.  Creare una funzione, in questo caso la chiamerò `getPageNumbers`.
2.  Creare variabili per il primo e l’ultimo elemento dell’elenco.
3.  Creare una variabile per l’ellissi sul lato sinistro. Per scelta personale, la mia ellissi si collocherà dopo il quarto elemento della lista.
4.  Creare una variabile per l’ellissi sul lato destro. Per scelta personale, la mia ellissi si collocherà prima degli ultimi tre elementi della lista.
5.  Creare una funzione che ci restituisca un array in cui siano sempre centrati 5 elementi: la pagina attuale, due elementi precedenti e due successivi. Se necessario, escluderemo la prima e l’ultima pagina.

## Blocco di codice

```
const pagesAroundCurrent = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2].filter(page => page > firstPage && page < lastPage);
```

6.  Per l’ultima variabile, creeremo un array che contenga tutte le variabili create in precedenza.
7.  Infine, filtreremo gli elementi null e restituiremo l’array.

Questo è l’array che itereremo per ottenere l’elenco degli elementi nella nostra paginazione nel modo seguente:

## Blocco di codice

```
<ol className="flex gap-3">
  {pageNumbers.map((number) => {
    const isEllipsis = number === "left-ellipsis" || number === "right-ellipsis"
    if (isEllipsis) {
      return (
        <li
          key={number}
          className="relative top-5"
          aria-label="Ellipsis, more pages between"
        >
          <span aria-hidden="true">…</span>
        </li>
      )
    }
    const page = Number(number)
    const isCurrent = currentPage === page
    return (
      <li
        key={`page-${number}`}
        aria-setsize={totalPages}
        aria-posinset={typeof number === "number" ? page : undefined}
      >
        <button
          onClick={() => goToPage(page)}
          className={isCurrent ? "underline underline-offset-3 border-zinc-300" : undefined}
          aria-label={`Go to page ${page}`}
          aria-current={isCurrent ? "page" : undefined}
        >
          {page}
        </button>
      </li>
    )
  })}
</ol>
```

E fin qui come realizzare una paginazione riutilizzabile e accessibile! Personalmente ho imparato a costruire una paginazione da zero a forza di tentativi, perché ho dovuto implementarla durante una sessione di live coding; spero che la mia esperienza vi sia d’aiuto per la vostra carriera e che possiate implementarla e magari perfino migliorarla!  
  
Un saluto,  
Mica
