A small news website is a great front-end practice project: it needs a clear page structure, a responsive grid of stories, navigation, search and a readable article layout. This guide builds one with semantic HTML, modern CSS and a little plain JavaScript, then shows how to add Bootstrap 5 if you prefer a framework, and how to put the finished site online.
Structure the page with semantic elements (header, nav, main, article, aside, footer), lay out stories with CSS Grid so the page adapts to any screen without media-query juggling, and load stories from a JSON file with fetch, building each card with textContent rather than innerHTML. Add a filter box and a sticky header, test with a keyboard, and upload the finished folder to public_html on any shared hosting plan.
1. Plan the page and the files
A news home page has five regions: a masthead with the site name, category navigation, a featured story, a grid of recent stories, and a sidebar of trending links. Keep the project small:
news-site/
├── index.html
├── styles.css
├── app.js
├── news.json
└── images/2. Write semantic HTML
Semantic elements tell browsers, screen readers and search engines what each part of the page is. Use them instead of a pile of divs.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>City Times: local news</title>
<meta name="description" content="Local news, politics, technology and sport.">
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="site-header">
<a class="brand" href="/">City Times</a>
<nav aria-label="Categories">
<ul>
<li><a href="#" aria-current="page">Home</a></li>
<li><a href="#politics">Politics</a></li>
<li><a href="#technology">Technology</a></li>
<li><a href="#sport">Sport</a></li>
</ul>
</nav>
<label class="search">
<span class="visually-hidden">Search stories</span>
<input id="search" type="search" placeholder="Search stories">
</label>
</header>
<main id="main" class="layout">
<section aria-labelledby="latest">
<h1 id="latest">Latest news</h1>
<div id="stories" class="grid"></div>
<p id="empty" hidden>No stories match your search.</p>
</section>
<aside aria-labelledby="trending">
<h2 id="trending">Trending</h2>
<ol id="trending-list"></ol>
</aside>
</main>
<footer class="site-footer">
<p>© 2026 City Times</p>
</footer>
<template id="story-card">
<article class="card">
<img alt="" loading="lazy" width="640" height="360">
<div class="card-body">
<p class="tag"></p>
<h2><a></a></h2>
<p class="summary"></p>
<time></time>
</div>
</article>
</template>
</body>
</html>Points worth copying: one h1 per page, a skip link for keyboard users, alt text on images (empty only when the image is decorative), and width and height on images so the layout does not jump while they load.
3. Style it with modern CSS
CSS Grid with auto-fill and minmax() creates a responsive card grid in one rule. Custom properties keep colours in one place, and prefers-color-scheme gives you a dark mode for free.
:root {
--bg: #ffffff; --text: #1a1a1a; --muted: #5c5c5c; --accent: #b00020; --card: #f6f6f6;
font-family: system-ui, sans-serif; line-height: 1.5;
}
@media (prefers-color-scheme: dark) {
:root { --bg: #121212; --text: #eeeeee; --muted: #aaaaaa; --accent: #ff6b6b; --card: #1e1e1e; }
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); }
img { max-width: 100%; height: auto; display: block; }
.site-header {
position: sticky; top: 0; z-index: 10;
display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between;
padding: 0.75rem 1rem; background: var(--bg); border-bottom: 3px solid var(--accent);
}
.site-header ul { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.site-header a { color: inherit; text-decoration: none; }
[aria-current="page"] { font-weight: 700; color: var(--accent); }
.layout { display: grid; gap: 2rem; max-width: 1200px; margin: 0 auto; padding: 1rem; }
@media (min-width: 900px) { .layout { grid-template-columns: 3fr 1fr; } }
.grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.card { background: var(--card); border-radius: 8px; overflow: hidden; }
.card-body { padding: 1rem; }
.card h2 { font-size: 1.15rem; margin: 0.25rem 0; }
.tag, time { color: var(--muted); font-size: 0.85rem; }
a:focus-visible, input:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 1rem; top: 1rem; background: var(--bg); padding: 0.5rem; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }4. Load stories from JSON
Keep the stories in news.json so you can change content without touching the HTML:
[
{
"title": "New metro line opens next month",
"summary": "The 12 km line links the airport with the city centre.",
"category": "Politics",
"url": "stories/metro-line.html",
"image": "images/metro.webp",
"date": "2026-09-20",
"trending": true
}
]// app.js
const grid = document.getElementById('stories');
const template = document.getElementById('story-card');
const empty = document.getElementById('empty');
let stories = [];
function render(list) {
grid.replaceChildren();
for (const s of list) {
const card = template.content.cloneNode(true);
card.querySelector('img').src = s.image;
card.querySelector('.tag').textContent = s.category;
const link = card.querySelector('h2 a');
link.textContent = s.title;
link.href = s.url;
card.querySelector('.summary').textContent = s.summary;
const time = card.querySelector('time');
time.dateTime = s.date;
time.textContent = new Date(s.date).toLocaleDateString('en-IN', { dateStyle: 'medium' });
grid.append(card);
}
empty.hidden = list.length > 0;
}
async function init() {
try {
const res = await fetch('news.json');
if (!res.ok) throw new Error(res.statusText);
stories = await res.json();
} catch {
grid.textContent = 'Stories could not be loaded. Please refresh the page.';
return;
}
render(stories);
const trending = document.getElementById('trending-list');
for (const s of stories.filter((s) => s.trending).slice(0, 5)) {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = s.url;
a.textContent = s.title;
li.append(a);
trending.append(li);
}
}
document.getElementById('search').addEventListener('input', (e) => {
const q = e.target.value.trim().toLowerCase();
render(stories.filter((s) => (s.title + ' ' + s.summary).toLowerCase().includes(q)));
});
init();Setting textContent means a headline containing a script tag is displayed as text, never run. That habit matters as soon as your content comes from anywhere other than your own file; at that point also check that each link starts with https: or / before using it.
fetch does not work when you open index.html straight from your disk. Run a local server instead, for example npx serve . (with Node.js installed) or the Live Server extension in VS Code.
5. Features to add next
grid-column: span 2, instead of a JavaScript carouselcategory and highlight the matching link with aria-currentarticle with a clear h1, byline, time and readable line length (about 65 characters, max-width: 65ch)Auto-rotating carousels are best avoided: readers rarely wait for later slides, and moving content is hard for many users. A large featured card does the job better.
6. Prefer Bootstrap? Use version 5
If you want ready-made components, use Bootstrap 5. Older Bootstrap 4 examples that load from the StackPath CDN, rely on jQuery or use classes such as sr-only are out of date. Copy the current CSS and JavaScript links, including their integrity hashes, from getbootstrap.com. In Bootstrap 5, visually-hidden replaces sr-only, the JavaScript needs no jQuery, and the card grid becomes row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 with a col around each card. The semantic structure from section 2 stays the same.
7. Check it before you publish
- Keyboard: press Tab through the page. Every link and the search box should show a visible focus outline.
- Mobile: use your browser's device toolbar at 360 px wide. Nothing should scroll sideways.
- Lighthouse: run it from Chrome DevTools and fix the accessibility and SEO items it flags.
- Images: export them as WebP or AVIF at the size they display, not straight from the camera.
8. Putting it online with Domain India
A site like this is just files, so any Domain India shared hosting plan can serve it:
- Zip the contentsof your
news-sitefolder, not the folder itself, soindex.htmlsits at the top of the zip. - Upload the zipwith your control panel's File Manager into
public_htmland extract it there. FTP works too. - Open your domain.Free SSL is included with hosting, so the site loads over HTTPS.
See How to use the File Manager and What settings do I need to upload my website. On cPanel, a page can be served from cache for up to 120 minutes, so add ?t=1 to the address to check a change straight away. If you would rather not write code at all, the AI Website Builder creates a site for you.
- 25 GB NVMe SSD Storage
- 50 GB Monthly Bandwidth
- 1 Website
- 10 Email Accounts
- 10 GB NVMe SSD Storage
- 50 GB Monthly Bandwidth
- 1 Website
- 5 Email Accounts
Do I need Bootstrap to build a responsive news website?
No. Modern CSS Grid and Flexbox handle responsive layouts in a few lines. Bootstrap 5 is useful if you want ready-made components, but it is optional.
Why does my JavaScript not load news.json when I open the file directly?
Browsers block fetch requests from pages opened as local files. Run a local web server, such as npx serve or the VS Code Live Server extension, and open the page through it.
Is innerHTML safe for inserting news headlines?
Not for content you do not fully control. Use textContent or build elements with createElement, so any HTML in a headline is shown as text instead of being run.
How do I add real news from a news API?
Most news APIs need a secret key, so call them from a small server-side script and send only the stories to the browser. Never put the key in front-end JavaScript.
Which HTML elements should a news page use?
header, nav, main, article, aside and footer, with one h1 per page and time elements for dates. They improve accessibility and help search engines understand the page.
How do I publish a static news site on Domain India?
Zip the site's files, upload the zip to public_html with the File Manager in your hosting control panel, and extract it. FTP also works, and free SSL is included with hosting.
Ready to publish your site? Get a domain name, pick cPanel hosting, or ask us in a support ticket if you need help uploading.
Upload your HTML, CSS and JavaScript with the File Manager or FTP, with free SSL included.
See hosting plans