How to Build a Custom News Headlines Widget for Your Website

Adding a custom news headlines widget to your website is a great way to provide value by displaying up-to-date breaking news directly on your homepage or other web pages. A well-designed widget can improve user engagement, establish your site as a go-to source for information, and even enhance your site’s SEO by keeping it dynamic and content-rich.

Whether you run a blog, a business website, or a personal portfolio, integrating a news widget ensures your visitors stay informed while spending more time exploring your content. This tutorial will guide you through building a custom news headlines widget using JavaScript. You’ll learn how to fetch breaking news from the ViewBits API, style the widget for seamless integration, and ensure it updates in real-time.

Not only will this project enhance your web development skills, but it will also provide a practical feature that you can implement on any site. By the end, you’ll have a functional, professional-looking news widget that can showcase headlines from your chosen news sources. This is an excellent way to make your website more engaging, modern, and relevant to your audience.

Getting Started

Building a custom news headlines widget doesn’t have to be complicated. In this tutorial, we’ll be using only simple HTML, CSS, and JavaScript—no need for complex setups or external libraries. This straightforward approach ensures that even beginners can follow along while providing enough flexibility for more experienced developers to customize the widget further.

With just a text editor and a browser, you’ll be able to create a fully functional, visually appealing widget that fetches and displays live news headlines. Whether you’re new to coding or looking for a quick project, this tutorial keeps things simple and accessible while delivering professional results.

Step 1: Build the HTML and CSS

The first step in creating your news headlines widget is to set up the basic structure and style using HTML and CSS. This section lays the groundwork for displaying news data and provides a functional starting point that you can easily customize to match your website’s design.

HTML

<h1 id="page-top">News Headlines</h1>
<ul id="headline-list"></ul>
<div id="pagination"></div>
<div id="countdown"></div>

The HTML defines the essential elements, including a title, a list for the headlines, and sections for pagination and a countdown timer. Meanwhile, the CSS adds basic styling to ensure the widget looks clean and organized right from the start.

CSS

<style>
#pagination{display: flex; justify-content: start;}
#headline-list{list-style: none;margin-left: -24px;}
#headline-list li{margin-bottom:1em;}
</style>

The provided code includes a simple <style> block for applying minimal styles to the widget. It ensures the pagination buttons are neatly aligned, the headlines list has no default bullet points, and spacing between list items is visually appealing. Together, these foundational elements create a straightforward yet functional base for displaying news headlines, ready to be expanded and tailored to suit your website’s specific needs.

Step 2: Define Key Variables

Before diving into the functionality of the widget, we need to set up some key variables that will be used throughout the JavaScript code. These variables help define important settings, such as how many headlines to display, how often the widget refreshes, and the structure for fetching and organizing data.

JavaScript

const headlineLimit = 100; // Speicfy max number of headlines to view
const headlinesPerPage = 12; // Specify number of headlines per page
const refreshInterval = 15 * 60 * 1000; // 15 min

const apiKey = 'YOUR_API_KEY'; // Your API Key (for testing)
const apiUrl = `https://api.viewbits.com/v1/headlines?&limit=${headlineLimit}&key=${apiKey}`; //API URL (For Testing)

// ViewBits API Url (for production) - hide your api key using a local wrapper (See Docs)
// const apiUrl = `api.viewbits.php?dataset=headlines&limit=${headlineLimit}`; // Example wrapper (for production, see docs)

let headlines = []; // Array to store the headline objects
let currentPage = 1; // Page to start on page load
let countdownTime = refreshInterval; // Match Countdown Time to Refresh Interval

In the example code, constants like headlineLimit and headlinesPerPage establish limits for data retrieval and display, while variables like headlines and currentPage keep track of the widget’s state.

By defining these variables at the start, you create a clear foundation for managing your widget’s functionality. This step ensures your script is flexible and ready to handle updates dynamically, keeping the widget efficient and easy to maintain.

Important: To avoid CORS limitations in JavaScript, you must either include an API key with your call or use a CURL function on the backend. Learn how to protect your API key and create your own ViewBits API wrapper using this guide.

Step 3: Fetching News Headlines

To retrieve the latest news headlines for the widget, we use the fetchHeadlines function. This is an asynchronous function that communicates with the news API and handles the data fetching process efficiently. The function sends a POST request to the API URL, including a payload in the request body to specify any additional data or filters needed.

JavaScript

async function fetchHeadlines()
{
try
{
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ /* POST request payload */ }),
});
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching headlines:', error);
return [];
}
} // End Function

Step 4: Displaying News Headlines

The updateHeadlines function is responsible for dynamically displaying the fetched headlines on the webpage. It takes the array of headlines as input and updates the HTML structure to show only the headlines relevant to the current page.

JavaScript

function updateHeadlines(headlines)
{
const headlineList = document.getElementById('headline-list');
headlineList.innerHTML = '';

const start = (currentPage - 1) * headlinesPerPage;
const end = start + headlinesPerPage;
const headlinesToShow = headlines.slice(start, end);

headlinesToShow.forEach(headline => {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item');
const date = new Date(headline.timestamp * 1000);
const options = { month: 'short', day: 'numeric' };
const formattedDate = date.toLocaleDateString('en-US', options);
listItem.innerHTML = `
<a class="fw-bold" href="${headline.link}" target="_blank">${headline.title}</a><br />
${headline.category} | ${formattedDate} | ${getRootDomain(headline.link)}
`;
headlineList.appendChild(listItem);
});

renderPagination(headlines.length);

} // End Function

Here’s how the function works:

  1. Clear the Existing List: It begins by selecting the #headline-list element and clearing its current content to prepare for the updated list of headlines.
  2. Determine Headlines to Show: Using pagination logic, it calculates the range of headlines to display based on the current page (currentPage) and the defined number of headlines per page (headlinesPerPage).
  3. Create and Append List Items: For each headline in the current batch, the function creates a new <li> element, formats its content to include the headline title, category, date, and source domain, and appends it to the list. The date is formatted for better readability, and each headline link opens in a new tab for user convenience.
  4. Render Pagination: Finally, the function calls renderPagination to update the pagination controls based on the total number of headlines.

Step 5: Extracting the News Source Domain

The getRootDomain function is a utility used to extract the root domain from a given URL, which is displayed alongside each headline in the widget. This makes it easy for users to identify the source of the news in a clean and concise format.

JavaScript

function getRootDomain(url) 
{
try
{
// Create a new URL object
const hostname = new URL(url).hostname;

// Split the hostname by dots
const parts = hostname.split('.');

// Get the last two parts (root domain)
if (parts.length > 2) {
return `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
}
return hostname; // Return as is if it's already simple (e.g., localhost)
} catch (error)
{
console.error("Invalid URL:", error);
return null;
}
} // End Function

This function ensures that the displayed source domains are both accurate and user-friendly, enhancing the readability of your news widget without overwhelming users with overly detailed or unnecessary URL components.

Step 6: Adding Pagination Controls

The renderPagination function dynamically creates pagination controls for the news headlines widget. It calculates the total number of pages based on the total number of headlines and the number of headlines displayed per page.

JavaScript

function renderPagination(totalHeadlines) 
{
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';

const totalPages = Math.ceil(totalHeadlines / headlinesPerPage);

for (let i = 1; i <= totalPages; i++)
{
const buttondiv = document.createElement('div');
buttondiv.classList.add('page-item');

const button = document.createElement('button');
button.classList.add('page-link');
button.textContent = i;
if (i === currentPage)
{
button.disabled = true;
button.classList.add('active');
}
button.addEventListener('click', () => {
currentPage = i;
updateHeadlines(headlines);
document.getElementById('page-top').scrollIntoView({ behavior: 'smooth' });
});
buttondiv.appendChild(button)
pagination.appendChild(buttondiv);
}
} // End Function

This function generates a button for each page, highlights the current page, and disables its corresponding button to provide a clear and user-friendly navigation experience. Clicking a pagination button updates the currentPage variable, re-renders the relevant headlines, and scrolls the view back to the top of the widget.

Pagination is essential for improving usability, as it allows users to navigate through large datasets in manageable chunks rather than being overwhelmed by an extensive list of headlines. It ensures a smooth and intuitive browsing experience.

Step 7: Refreshing the Headlines

The refreshHeadlines function ensures that the widget always displays the most up-to-date news headlines. It fetches the latest headlines using the fetchHeadlines function, updates the displayed headlines via the updateHeadlines function, and resets the countdown timer to the specified refresh interval.

JavaScript

async function refreshHeadlines() 
{
headlines = await fetchHeadlines();
updateHeadlines(headlines);
countdownTime = refreshInterval; // Reset
}

refreshHeadlines(); //Initial load
setInterval(refreshHeadlines, refreshInterval);

This process is initiated with an initial call to refreshHeadlines when the widget loads, ensuring the headlines are populated immediately. To keep the widget current, the setInterval method is used to automatically refresh the headlines at regular intervals (e.g., every 15 minutes).

This feature is important for keeping the widget relevant and dynamic, particularly for displaying breaking news or rapidly changing stories. It automates updates, saving users from having to manually refresh the page to see the latest information.

Step 8: Adding a Countdown Timer (Optional Feature)

The updateCountdown function is an optional feature that provides users with a visible countdown, showing how much time remains until the next news update. This adds a dynamic element to the widget, giving users a clear idea of when new headlines will be available.

JavaScript

function updateCountdown() 
{
const countdownElement = document.getElementById('countdown');
const minutes = Math.floor(countdownTime / 60000);
const seconds = Math.floor((countdownTime % 60000) / 1000);
countdownElement.innerHTML = `Next news update in: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
countdownTime -= 1000;
if (countdownTime < 0) {countdownTime = refreshInterval;}
} // End Function

setInterval(updateCountdown, 1000);

The function calculates the remaining time by converting the countdownTime (in milliseconds) into minutes and seconds. It then updates the content of the #countdown element to display the countdown in a user-friendly format (e.g., “Next news update in: 5:30”). Each second, the countdownTime is decremented by 1, and once it reaches 0, it resets to the specified refreshInterval to start the countdown again.

This countdown feature is helpful for users who want to know when the news will refresh. It adds a sense of anticipation and enhances user engagement with the news widget.

Live Example

To see the full code in action, check out this live working example: https://api.viewbits.com/examples/headlines-widget.html

Full Code Implementation

That’s it! You’ve now built a fully functional news headlines widget that fetches live data, displays headlines with pagination, and optionally shows a countdown timer for the next update. Below is the complete code implementation for you to use and customize as needed.

Simply copy and paste it into your project, and you’ll have a dynamic and engaging news feed that you can easily integrate into your website. Whether you’re using it for breaking news, blog updates, or any other content, this widget is a great addition to keep your site fresh and interactive.

JavaScript

const headlineLimit = 100; // Speicfy max number of headlines to view
const headlinesPerPage = 12; // Specify number of headlines per page
const refreshInterval = 15 * 60 * 1000; // 15 min

const apiKey = 'YOUR_API_KEY'; // Your API Key (for testing)
const apiUrl = `https://api.viewbits.com/v1/headlines?&limit=${headlineLimit}&key=${apiKey}`; //API URL (For Testing)

// ViewBits API Url (for production) - hide your api key using a local wrapper (See Docs)
// const apiUrl = `api.viewbits.php?dataset=headlines&limit=${headlineLimit}`; // Example wrapper (for production, see docs)

let headlines = []; // Array to store the headline objects
let currentPage = 1; // Page to start on page load
let countdownTime = refreshInterval; // Match Countdown Time to Refresh Interval

async function fetchHeadlines()
{
try
{
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ /* POST request payload */ }),
});
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching headlines:', error);
return [];
}
} // End Function

function updateHeadlines(headlines)
{
const headlineList = document.getElementById('headline-list');
headlineList.innerHTML = '';

const start = (currentPage - 1) * headlinesPerPage;
const end = start + headlinesPerPage;
const headlinesToShow = headlines.slice(start, end);

headlinesToShow.forEach(headline => {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item');
const date = new Date(headline.timestamp * 1000);
const options = { month: 'short', day: 'numeric' };
const formattedDate = date.toLocaleDateString('en-US', options);
listItem.innerHTML = `
<a class="fw-bold" href="${headline.link}" target="_blank">${headline.title}</a><br />
${headline.category} | ${formattedDate} | ${getRootDomain(headline.link)}
`;
headlineList.appendChild(listItem);
});

renderPagination(headlines.length);

} // End Function

function getRootDomain(url)
{
try
{
// Create a new URL object
const hostname = new URL(url).hostname;

// Split the hostname by dots
const parts = hostname.split('.');

// Get the last two parts (root domain)
if (parts.length > 2) {
return `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
}
return hostname; // Return as is if it's already simple (e.g., localhost)
} catch (error)
{
console.error("Invalid URL:", error);
return null;
}
} // End Function

function renderPagination(totalHeadlines)
{
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';

const totalPages = Math.ceil(totalHeadlines / headlinesPerPage);

for (let i = 1; i <= totalPages; i++)
{
const buttondiv = document.createElement('div');
buttondiv.classList.add('page-item');

const button = document.createElement('button');
button.classList.add('page-link');
button.textContent = i;
if (i === currentPage)
{
button.disabled = true;
button.classList.add('active');
}
button.addEventListener('click', () => {
currentPage = i;
updateHeadlines(headlines);
document.getElementById('page-top').scrollIntoView({ behavior: 'smooth' });
});
buttondiv.appendChild(button)
pagination.appendChild(buttondiv);
}
} // End Function

async function refreshHeadlines()
{
headlines = await fetchHeadlines();
updateHeadlines(headlines);
countdownTime = refreshInterval; // Reset
}

refreshHeadlines(); //Initial load
setInterval(refreshHeadlines, refreshInterval);

function updateCountdown()
{
const countdownElement = document.getElementById('countdown');
const minutes = Math.floor(countdownTime / 60000);
const seconds = Math.floor((countdownTime % 60000) / 1000);
countdownElement.innerHTML = `Next news update in: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
countdownTime -= 1000;
if (countdownTime < 0) {countdownTime = refreshInterval;}
} // End Function

setInterval(updateCountdown, 1000);