How to Implement Pagination with HTML, CSS and JavaScript
On the Web, pagination is a way to break up large pieces of content into more bite-sized pieces. In this article, we’ll look at a simple way to divide content into a series of “pages” using HTML, CSS and vanilla JavaScript.
Although pagination can be implemented using frameworks such as React and Angular, the aim of this article is to provide a straightforward, step-by-step guide to setting up pagination, so that we can understand the basic concepts involved.
Creating Our Base Web Page
Before implementing our pagination system, let’s create an HTML structure that stores the content we want to display. This can be any kind of content, but for this tutorial, we’ll use a table of five columns and 15 rows that stores the names of students in different grades. Here’s a snippet of our HTML:
<article class="content">
<table>
<thead>
<tr>
<th>Grade 1</th>
<th>Grade 2</th>
<th>Grade 3</th>
<th>Grade 4</th>
<th>Grade 5</th>
</tr>
</thead>
<tbody>
<tr>
<td>Faith Andrew</td>
<td>Angela Christopher`</td>
<td>David Elias</td>
<td>Samuel Thomas</td>
<td>Richard Elias</td>
</tr>
⋮
</tbody>
</table>
</article>
We’ve wrapped the table in a container element (<article class="content">
). While we don’t strictly need a container element, it’s handy to have it, especially if there are other elements on our page. (It gives a useful context for the pagination buttons that we’ll be adding.)
You can view our full HTML code, along with some styling, on CodePen.
Implementing the Pagination Functionality with JavaScript
With our HTML and CSS in place, the next step is to implement pagination. We’ll firstly use JavaScript to divide the table into different “pages” and to add button functionality for navigating through those pages.
Creating a function that divides the table into pages
Here’s our code for dividing the table into separate pieces:
document.addEventListener('DOMContentLoaded', function () {
const content = document.querySelector('.content');
const itemsPerPage = 5;
let currentPage = 0;
const items = Array.from(content.getElementsByTagName('tr')).slice(1);
The first line creates an event listener that ensures that the JavaScript code runs after the HTML content has been fully loaded and parsed. This is to prevent any manipulation or interaction with elements before the content becomes available in the DOM.
With document.querySelector('.content')
, we’re selecting the <article class="content">
wrapper and initializing it as a variable.
With const itemsPerPage = 5;
, we’re setting the number of rows to display on each page.
With let currentPage = 0;
, we’re creating a variable that keeps track of the current page number. It starts at 0, which represents the first page. (The first index in JavaScript is 0, so it counts from 0 instead of 1.)
The last line uses the getElementsByTagName
method to select all the elements with a <tr>
tag within the table. We create an array (items
) of all the child elements and used the slice(1)
to exclude the first row (header) and create an array of the remaining rows.
This means that the heading will remain in place as we switch pages.
Working out the showPage() functionality
Next, let’s work on the code for showing pages:
function showPage(page) {
const startIndex = page * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
items.forEach((item, index) => {
item.classList.toggle('hidden', index < startIndex || index >= endIndex);
});
updateActiveButtonStates();
}
We start by creating a showPage()
function that accepts a page
parameter. This function is responsible for displaying the items connected to that specific page when it’s called.
Next, we calculate the startIndex
, which is the first item that should be displayed on the current page by multiplying the page parameter with the itemsPerPage
. We also calculate the endIndex
that comes immediately after the last item that should be displayed on the current page.
By doing this, we’re creating a range of items to be displayed. For example, let’s say we have ten items and we want to display five items per page. If we’re on the first page (page = 0), startIndex
will be 0, and endIndex
will be 0 + 5 = 5. This range ([0, 5]) includes the first five items. On the next page (page = 1), startIndex will be 5, and endIndex
will be 5 + 5 = 10. This range ([5, 10]) includes the remaining items.
With items.forEach()
, we create a loop that iterates through each row and checks if its index falls within the range of items to be displayed on the current page — that is, if it’s either before the startIndex
or after/equal to the endIndex
. If the index is within the range, the toggle
keyword applies the hidden
class (which we’ll define in our CSS code) to the item, effectively hiding it. If the index doesn’t meet either condition, the hidden
class is removed, making the item visible.
Our hidden
class moves the items off screen, hiding them from view but still allowing them to be accessible to those using screen readers:
.hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Adding buttons
Let’s now look at how to add our navigation buttons. In the code below, we’ll create and add the button functionality based on the content of the table:
function createPageButtons() {
const totalPages = Math.ceil(items.length / itemsPerPage);
const paginationContainer = document.createElement('div');
const paginationDiv = document.body.appendChild(paginationContainer);
paginationContainer.classList.add('pagination');
Firstly, we create a createPageButtons()
function that will store the logic to create our buttons. Then we calculate the total pages we’ll need to display our table. We do this by dividing the total number of items by the desired number of items per page. The result is rounded up using the Math.ceil()
function. This ensures that all the rows of our table items are covered by the available pages.
Next, we create a div to contain our dynamically generated page buttons (document.createElement('div')
). Then we appended the <div>
element to the body of our HTML structure using document.body.appendChild(paginationDiv)
. (We haven’t actually told it where to sit in the HTML structure yes. We’ll do that shortly.) Lastly, we add a class of pagination
to that button container so that we can target it with styles.
The next step is to create buttons for each page, using a loop to iterate through each possible page index:
for (let i = 0; i < totalPages; i++) {
const pageButton = document.createElement('button');
pageButton.textContent = i + 1;
pageButton.addEventListener('click', () => {
currentPage = i;
showPage(currentPage);
updateActiveButtonStates();
});
The for
loop ranges from 0 (which is the first page) to the total number of pages minus 1.
Within each page iteration, a new individual page button is created using the document.createElement()
method, increasing the page number by 1 each time it loops.
Next, we create a click event listener and attach it to the page buttons. When a button is clicked, the event listener’s callback function gets executed.
Here’s an explanation of the callback function:
- The
currentPage
variable is updated to the current value ofi
, which corresponds to the index of the clicked page. - The
showPage()
function is called with the updatedcurrentPage
value, causing the content of the clicked page to be displayed.
To finish off our button creation code, we end with this:
content.appendChild(paginationContainer);
paginationDiv.appendChild(pageButton);
We append our button container to the end of our .content
wrapper, and then place our buttons inside the button container.
Highlighting active buttons
To make our buttons more user-friendly, we’ll add a distinctive style to the currently “active” button. Let’s create a function that applies the styles of the active
CSS class to a button once its page is active:
function updateActiveButtonStates() {
const pageButtons = document.querySelectorAll('.pagination button');
pageButtons.forEach((button, index) => {
if (index === currentPage) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
}
First, we retrieve all the pagination buttons using the document.querySelectorAll
and assign them to the pageButtons
variable.
The updateActiveButtonStates()
function then goes through each of these buttons one by one, using a forEach
loop, and compares its index with the value of the currentPage
variable.
Next, we use the conditional if
statement to assign the styles of the active
class if the button’s index matches the current page.
If the button’s index doesn’t match the current page, the active
class is removed. This ensures that the other buttons don’t retain the active
class.
To implement this feature, we call the updateActiveButtonStates()
function whenever a page is changed or displayed.
Calling on the script
Our pagination script ends with the following two lines:
createPageButtons();
showPage(currentPage);
We call the createPageButtons()
function before the showPage()
function. This ensures that the buttons are created once the page loads.
Our script now calculates the appropriate range of items to display for each page, listens for button clicks, and updates the page display.
The final result
The following Pen shows the final result.
See the Pen
Pagination with HTML, CSS and JS, Step 2: Adding the JavaScript by SitePoint (@SitePoint)
on CodePen.
Adapting Our Code to Other Scenarios
The script we’ve created is handy for breaking up a table into a series of pages. But what if our content is something other than a table? Instead of table content, let’s try our script with some other kinds of content.
Pagination for section elements
Instead of a table element, let’s place some <section>
elements inside our container and see how to adapt our script. Here’s our basic HTML:
<article class="content">
<section></section>
<section></section>
<section></section>
<section></section>
<section></section>
</article>
We only need to make three very simple changes to our script:
document.addEventListener('DOMContentLoaded', function () {
const content = document.querySelector('.content');
const itemsPerPage = 1;
let currentPage = 0;
const items = Array.from(content.getElementsByTagName('section')).slice(0);
The changes are:
- set
itemsPerPage
to 1, so that only one section appears per page - change the targeted tag name to
section
, as we’re now looping through<section>
elements rather than<tr>
elements - set
slice()
to 0, which limits the selection to the first section element (which has index 0)
The following CodePen demo shows this in action.
See the Pen
CodePen Home Pagination with HTML, CSS and JS: sections by SitePoint (@SitePoint)
on CodePen.
Pagination for an unordered list
We can easily adapt the demo above to work with a list of items. In the example below, we change the wrapping element from an <article>
to a <ul>
, and change the <section>
elements to <li>
elements:
<ul class="content">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
In our JavaScript, we’ll just make two changes:
getElementsByTagName('section')
becomesgetElementsByTagName('li')
- let’s set
const itemsPerPage
to2
to show two list items per page
After some minor CSS changes to account for the unordered list, we end up with the result below.
See the Pen
Pagination with HTML, CSS and JS: unordered list by SitePoint (@SitePoint)
on CodePen.
Conclusion
In this tutorial, we learned how to implement pagination using HTML, CSS and JavaScript. For those without JavaScript enabled (for whatever reason), the full content is still available — just without pagination. By using semantic <button>
elements, the page is still keyboard accessible. We’ve also hidden our non-active content by moving it off screen, rather than using display: none
, so that it’s still accessible to screen readers.
We could go further by adding descriptive ARIA labels and attributes to convey the purpose and role of elements such as pagination buttons to screen readers.
I hope this demo will get you thinking about simple pagination functionality without needing to reach for a framework.