Front-end Machine Coding Series #4: Building a Button-Based Pagination UI in Vanilla JavaScript with Vanilla JS

Nitin SainiNitin Saini
4 min read

In modern web applications, displaying large datasets like product listings, user directories, or news feeds, without overwhelming the user is a common challenge. Instead of dumping everything on the screen, pagination offers a clean, performance-friendly solution: breaking content into digestible, navigable pages.

While libraries like React Paginate or UI kits like Material UI offer pre-built components, building pagination from scratch using Vanilla JavaScript is a great way to sharpen your skills in DOM manipulation, array slicing, and dynamic rendering logic.

In this blog, we'll walk through creating a pagination UI that allows users to:

  • View N products per page

  • Navigate with Previous, Next, and numbered page buttons

  • Highlight the active page dynamically

  • Prevent navigating beyond available pages

Problem Statement

Design and implement a client-side pagination system using plain HTML, CSS, and JavaScript—without using any frameworks or libraries.

Requirements:

  • We’ll be using https://dragonball-api.com/api/characters from fetching the products

  • Display 10 products per page

  • Show page numbers, Previous (⬅️), and Next (➡️) buttons

  • Highlight the active page

  • Disable Prev/Next when at the first/last page

HTML Page:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pagination in JS</title>
    <link rel="stylesheet" href="./index.css" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="app-container"></div>
    <script src="./pagination.js"></script>
  </body>
</html>

CSS Code:

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: "Manrope", sans-serif;
}

.products {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 1rem;
}

.single-product {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 20rem;
}

img {
  width: 7rem;
  height: 12rem;
}

.pagination {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 20px;
  flex-wrap: wrap;
}

.pagination button {
  padding: 8px 12px;
  border: none;
  border-radius: 6px;
  background-color: #eee;
  color: #333;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.pagination button:hover:not(.disabled):not(.active) {
  background-color: #ddd;
}

.pagination button.active {
  background-color: #007bff;
  color: white;
  font-weight: bold;
}

.pagination button.disabled {
  background-color: #ccc;
  color: #777;
  cursor: not-allowed;
  pointer-events: none;
}

JavaScript code:

document.addEventListener("DOMContentLoaded", () => {
  const appContainer = document.querySelector(".app-container");
  let products = [];
  let page = 1;

  const fetchProducts = async () => {
    try {
      const apiResponse = await fetch(
        "https://dragonball-api.com/api/characters?limit=50"
      );
      const data = await apiResponse.json();
      products = data?.items;
      console.log(products);
      if (products?.length > 0) {
        renderUI();
      }
    } catch (error) {
      console.log("Error in fetching API data");
    }
  };

  fetchProducts();

  const renderUI = () => {
    const productsContainer = document.createElement("div");
    productsContainer.classList.add("products");
    //creating pagination div
    let pagination = document.createElement("div");
    pagination.classList.add("pagination");

    //creating product cards
    if (products?.length > 0) {
      products?.slice(page * 10 - 10, page * 10)?.forEach((product) => {
        const productElement = document.createElement("div");
        productElement.classList.add("single-product");
        productElement.innerHTML = `
            <img src="${product?.image}"/>
            <h3 class="title">${product?.name}</h3>
            <p class="description">${product?.description?.slice(0, 200)}</p>
            `;
        productsContainer.appendChild(productElement);
      });

      //creating previous button
      if (page > 1) {
        const previous = createPaginationButton(
          "⬅️",
          () => {
            pageHandler(page - 1);
          },
          false,
          page === 1
        );
        pagination.appendChild(previous);
      }

      //display middle numbers
      for (let i = 0; i < products?.length / 10; i++) {
        const middle = createPaginationButton(
          i + 1,
          () => {
            pageHandler(i + 1);
          },
          page === i + 1
        );
        pagination.appendChild(middle);
      }

      //creating next button
      if (page < products?.length / 10) {
        const next = createPaginationButton(
          "➡️",
          () => {
            pageHandler(page + 1);
          },
          false,
          page === Math.ceil(products.length / 10)
        );
        pagination.appendChild(next);
      }
    }
    //so that previous page content doesn't get added to next page
    appContainer.innerHTML = "";
    appContainer.appendChild(productsContainer);
    appContainer.appendChild(pagination);
  };

  const createPaginationButton = (
    text,
    clickHandler,
    isActive = null,
    isDisabled = false
  ) => {
    const button = document.createElement("button");
    button.textContent = text;
    button.addEventListener("click", clickHandler);
    if (isActive) {
      button.classList.add("active");
    }
    if (isDisabled) {
      button.disabled = true;
      button.classList.add("disabled"); // Optional: for styling
    }
    return button;
  };

  //for added new page content
  const pageHandler = (selectedPage) => {
    if (
      selectedPage >= 1 &&
      selectedPage <= products?.length / 10 &&
      selectedPage !== page
    ) {
      page = selectedPage;
      renderUI();
    }
  };
});

Wrap Up

Pagination is a crucial UI/UX pattern for navigating large datasets. In this project, you’ve learned how to build a fully-functional pagination system using only Vanilla JavaScript. You can now apply this logic to blogs, product listings, user directories, and more.

Happy coding! If you have any questions or suggestions you'd like to explore further, feel free to drop a comment below.

See you in the next blog. Please don’t forget to follow me:

Twitter
LinkedIn

0
Subscribe to my newsletter

Read articles from Nitin Saini directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nitin Saini
Nitin Saini

A Full Stack Web Developer, possessing a strong command of React.js, Node.js, Express.js, MongoDB, and AWS, alongside Next.js, Redux, and modern JavaScript (ES6+)