Accessible components: Pagination

Today, we’re going to look at how to create pagination from scratch and make it accessible and reusable. I hope it’s helpful, and please leave your comments at the end of the post!

Github: https://github.com/micaavigliano/accessible-pagination
Proyect…


This content originally appeared on DEV Community and was authored by Mica

Today, we're going to look at how to create pagination from scratch and make it accessible and reusable. I hope it’s helpful, and please leave your comments at the end of the post!

Github: https://github.com/micaavigliano/accessible-pagination
Proyecto: https://accessible-pagination.vercel.app/

Custom hook to fetch data

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. We'll create a custom hook with a generic type. This will allow us to specify the expected data type when using this hook.
  2. We'll expect three parameters: one for the URL from which we'll fetch the data, currentPage, which is the page we're on (default is 0), and pageSize, which is the number of items per page (default is 20, but you can change this value).
  3. In our state const [data, setData] = useState<T | null>(null);, we use the generic type T since, as we use it for different data requests, we'll be expecting different data types.

Paginación

To make pagination accessible, we need to consider the following points:

  • Focus should move through all interactive elements of the pagination and have a visible indicator.
  • To ensure good interaction with screen readers, we must correctly use regions, properties, and states.
  • Pagination should be grouped within a <nav> tag and contain an aria-label identifying it specifically as pagination.
  • Each item within the pagination should have an aria-setsize and an aria-posinset. Now, what are these for? Well, aria-setsize is used to calculate the total number of items within the pagination list. The screen reader will announce it as follows:

Screenshot of a voiceover annoucement: list of 1859 items

aria-posinset is used to calculate the position of the item within the total number of items in the pagination. The screen reader will announce it as follows:

Screenshot of the screen reader voiceover that announces: go to page 1. Current page, button, position 1 of 1859

  • Each item should have an aria-label to indicate which page we’ll go to if we click on that button.
  • Include buttons to go to the next/previous item, and each of these buttons should have its corresponding aria-label.
  • If our pagination contains an ellipsis, it should be correctly marked with an aria-label.
  • Every time we go to a new page, the screen reader should announce which page we are on and how many new items there are, as follows:

Screenshot of the screen reader voiceover that announces: page 3 loaded. Showing 20 items

To achieve this, we’re going to code it as follows:

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]);

When the page finishes loading, we’ll set a new message with our currentPage and the length of the new array we’re loading.

Alright! Now let’s take a look at how the code is structured in the pagination.tsx file.

The component will require five props.

interface PaginationProps {
  currentPage: number;
  totalPages: number;
  nextPage: () => void;
  prevPage: () => void;
  goToPage: (page: number) => void;
}
  • currentPage refers to the current page. We’ll manage it within the component where we want to use pagination as follows: const [currentPage, setCurrentPage] = useState<number>(1);
  • totalPages refers to the total number of items to display that the API contains.
  • nextPage is a function that will allow us to go to the next page and update our currentPage state as follows:
const handlePageChange = (newPage: number) => {
    setCurrentPage(newPage); 
  };

  const nextPage = () => {
    if (currentPage < totalPages) {
      handlePageChange(currentPage + 1);
    }
  };
  • prevPage is a function that will allow us to go to the page before our current page and update our currentPage state.
const prevPage = () => {
    if (currentPage > 1) {
      handlePageChange(currentPage - 1);
    }
  };
  • goToPage is a function that will need a numeric parameter and is the function each item will use to navigate to the desired page. We’ll make it work as follows:
const handlePageChange = (newPage: number) => {
    setCurrentPage(newPage); 
};

To bring our pagination to life, we need one more step: creating the array that we’ll iterate through in our list! For that, we should follow these steps:

  1. Create a function; I’ll call it getPageNumbers in this case.
  2. Create variables for the first and last items in the list.
  3. Create a variable for the left-side ellipsis. I’ve decided to place my ellipsis after the fourth item in the list.
  4. Create a variable for the right-side ellipsis. I’ve decided to place my ellipsis just before the last three items in the list.
  5. Create a function that returns an array with 5 centered items: the current page, two previous items, and two subsequent items. If needed, we’ll exclude the first and last pages. const pagesAroundCurrent = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2].filter(page => page > firstPage && page < lastPage);
  6. For our final variable, we’ll create an array that contains all the previously created variables.
  7. Finally, we’ll filter out null elements and return the array. This array is what we’ll iterate through to get the list of items in our pagination as follows:
<ol className='flex gap-3'>
          {pageNumbers.map((number) => {
            if (number === 'left-ellipsis' || number === 'right-ellipsis') {
              return (
                <span key={number} className='relative top-5' aria-label='ellipsis'>
                  ...
                </span>
              );
            }
            return (
              <li aria-setsize={totalPages} aria-posinset={typeof number === 'number' ? number : undefined} key={`page-${number}`}>
                <button
                  onClick={() => goToPage(Number(number))}
                  className={currentPage === Number(number) ? 'underline underline-offset-3 border-zinc-300' : ''}
                  aria-label={`go to page ${number}`}
                  aria-current={currentPage === Number(number) && 'page'}
                >
                  {number}
                </button>
              </li>
            );
          })}
        </ol>

And that’s how to create a reusable and accessible pagination! Personally, I learned how to build pagination from scratch the hard way because I had to implement it in a live coding session. I hope my experience helps you in your career and that you can implement it and even improve it!

You can follow me:
linkedin: https://www.linkedin.com/in/micaelaavigliano/

Best regards,
Mica <3


This content originally appeared on DEV Community and was authored by Mica


Print Share Comment Cite Upload Translate Updates
APA

Mica | Sciencx (2024-10-29T16:58:05+00:00) Accessible components: Pagination. Retrieved from https://www.scien.cx/2024/10/29/accessible-components-pagination/

MLA
" » Accessible components: Pagination." Mica | Sciencx - Tuesday October 29, 2024, https://www.scien.cx/2024/10/29/accessible-components-pagination/
HARVARD
Mica | Sciencx Tuesday October 29, 2024 » Accessible components: Pagination., viewed ,<https://www.scien.cx/2024/10/29/accessible-components-pagination/>
VANCOUVER
Mica | Sciencx - » Accessible components: Pagination. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/10/29/accessible-components-pagination/
CHICAGO
" » Accessible components: Pagination." Mica | Sciencx - Accessed . https://www.scien.cx/2024/10/29/accessible-components-pagination/
IEEE
" » Accessible components: Pagination." Mica | Sciencx [Online]. Available: https://www.scien.cx/2024/10/29/accessible-components-pagination/. [Accessed: ]
rf:citation
» Accessible components: Pagination | Mica | Sciencx | https://www.scien.cx/2024/10/29/accessible-components-pagination/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.