CSS Navigation Bars – Vertical and Horizontal Navbars
Lesson 32: CSS Navigation Bars — Building Vertical and Horizontal Navbars
Lesson Introduction
Every website you have ever visited has a navigation bar — also called a navbar. It is the row of links at the top of a page (or the column of links down the side) that lets visitors jump between different sections or pages.
Navigation bars are one of the first things a visitor sees when they land on a website. A clean, well-styled navbar instantly makes a site feel professional. A clunky, unstyled one — just a plain blue list of underlined links — signals an unfinished project.
The great news is that building a beautiful, fully functional navbar in CSS is completely achievable for a beginner. It is mostly a matter of:
- Starting with the right HTML structure (a
<ul>list of links) - Removing the default list styles that make it look ugly
- Applying positioning, colours, hover effects, and spacing
In this lesson you will learn:
- Why a
<ul>list is the correct HTML foundation for any navbar - How to strip out default list styling to get a clean starting point
- How to build a vertical navigation bar (links stacked top to bottom, like a sidebar)
- How to build a horizontal navigation bar (links side by side in a row, like a top menu)
- How to style link states: normal, visited, hover, and active
- How to add dropdown menus, borders, dividers, and sticky positioning
- How to combine everything into a complete, professional website header
By the end you will have built two complete navbars and a full website header mini-project.
Prerequisite Concepts
Before diving in, let us quickly review three things this lesson depends on.
The <a> Tag and Link States
A link (<a>) has four special states that CSS can style separately:
a:link— an unvisited link (the default blue underlined look)a:visited— a link the user has already clicked (usually turns purple)a:hover— when the user’s mouse is over the linka:active— the brief moment the user is clicking the link
Order matters! Always write them in this order:
link → visited → hover → active. A helpful memory trick: LoVe HAte (Link, Visited, Hover, Active).
display: block vs display: inline
By default, <a> tags are inline elements — they only take up as much space as their text content. This makes them hard to click precisely, especially on mobile.
Making a link display: block turns it into a block element that fills the full width of its container. This gives it a much larger, easier-to-click area — very important for navigation links.
Lists and Default Styles
An unordered list (<ul>) by default comes with:
- Bullet points (
list-style-type: disc) - Indentation (left padding and margin)
- Vertical stacking of items
For navbars, we always start by removing these defaults:
ul {
list-style-type: none; /* removes bullets */
margin: 0; /* removes default margin */
padding: 0; /* removes default indentation */
}
Part 1: Why Use a List for Navigation?
You might wonder: why start with a <ul> list? Why not just a row of <div> elements or plain <a> tags?
The answer is semantic HTML — using HTML elements for their correct intended meaning. A navigation menu is literally a list of links. Using <ul> and <li> tells the browser, search engines, and screen readers (used by visually impaired users) exactly what this content is.
This matters for:
- Accessibility: Screen readers announce “list of 5 items” to blind users, helping them understand the navigation structure.
- SEO: Search engines give weight to navigation links, and proper structure helps them understand your site.
- Maintainability: Other developers instantly recognise the pattern.
The Standard HTML Structure for Every Navbar:
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
<nav>— the semantic HTML5 element that marks a navigation section<ul>— the unordered list container<li>— each individual list item (one per link)<a>— the actual clickable link
This HTML alone, unstyled, looks like a plain bulleted list. CSS is what transforms it into a polished navbar.
Part 2: The Starting Reset — Stripping Default List Styles
The very first step before building any navbar is always to remove the browser’s default list styling. This is your clean canvas.
/* Step 1: Remove default list appearance */
ul {
list-style-type: none; /* kills the bullet points */
margin: 0; /* kills the default outer spacing */
padding: 0; /* kills the default left indentation */
}
Before (without reset):
- Bullet points on every item
- Items indented from the left
- Links appear as blue underlined text
After (with reset):
- Plain text items with no bullets
- No indentation
- Ready to be styled however you like
Thinking Prompt: Open any website in your browser. Right-click the navigation bar and choose “Inspect”. Can you find the
<ul>and<li>elements that make up the nav? Nearly every website uses this pattern.
Part 3: Building a Vertical Navigation Bar
A vertical navbar shows links stacked on top of each other, like a sidebar menu. It is common on dashboards, admin panels, documentation sites, and mobile menus.
Step 1: The Base HTML
<!DOCTYPE html>
<html>
<head>
<title>Vertical Navbar</title>
<link rel="stylesheet" href="vertical-nav.css">
</head>
<body>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#news">News</a></li>
<li><a href="#contact">Contact</a></li>
<li><a href="#about">About</a></li>
</ul>
</nav>
</body>
</html>
Step 2: The Core CSS — Making It Vertical
/* vertical-nav.css */
/* Reset */
ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 200px; /* the navbar has a fixed width */
background-color: #f1f1f1; /* light grey background */
}
/* Each link */
li a {
display: block; /* CRUCIAL — makes the entire area clickable, not just the text */
color: #333; /* dark grey text */
padding: 10px 16px; /* comfortable vertical and horizontal breathing room */
text-decoration: none; /* removes the underline */
}
Expected Output (so far):
- A 200px-wide light grey sidebar
- Four links stacked vertically, each with comfortable padding
- No underlines, no bullets, clean text
Step 3: Adding Hover and Active States
/* Change background on hover */
li a:hover {
background-color: #555; /* dark grey */
color: white;
}
/* Highlight the currently active/selected page */
li a.active {
background-color: #04aa6d; /* green — clearly shows which page is current */
color: white;
}
Update the HTML — add class="active" to the current page’s link:
<li><a href="#home" class="active">Home</a></li>
Expected Output:
- Hovering over any link turns the background dark grey with white text.
- The “Home” link has a permanent green background showing it is the current page.
Step 4: Adding a Border
ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 200px;
background-color: #f1f1f1;
border: 1px solid #ccc; /* a subtle outer border for the whole navbar */
}
/* Divider lines between items */
li {
border-bottom: 1px solid #ccc; /* each item has a line underneath it */
}
/* Remove border from last item so it doesn't double up with the outer border */
li:last-child {
border-bottom: none;
}
Expected Output:
- A bordered navbar with a thin line between each link item — like a clean menu card.
Complete Vertical Navbar — Full Code
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
margin: 0;
}
ul.vertical-nav {
list-style-type: none;
margin: 0;
padding: 0;
width: 220px;
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 6px;
overflow: hidden;
}
ul.vertical-nav li {
border-bottom: 1px solid #ddd;
}
ul.vertical-nav li:last-child {
border-bottom: none;
}
ul.vertical-nav li a {
display: block;
color: #333;
padding: 12px 18px;
text-decoration: none;
font-size: 15px;
transition: background-color 0.2s, color 0.2s; /* smooth colour change */
}
ul.vertical-nav li a:hover {
background-color: #0078d7;
color: white;
}
ul.vertical-nav li a.active {
background-color: #0078d7;
color: white;
font-weight: bold;
}
</style>
</head>
<body>
<nav style="padding: 30px;">
<ul class="vertical-nav">
<li><a href="#home" class="active">🏠 Home</a></li>
<li><a href="#news">📰 News</a></li>
<li><a href="#contact">✉️ Contact</a></li>
<li><a href="#about">ℹ️ About</a></li>
</ul>
</nav>
</body>
</html>
Expected Output:
- A polished sidebar nav 220px wide with rounded corners.
- Each item has a thin divider line.
- “Home” has a blue background (active state).
- Hovering over other links smoothly transitions to a blue background with white text.
Part 4: Centering Content Next to a Vertical Navbar
In a real webpage, a vertical navbar sits beside the main content. Here is a simple two-column layout using Flexbox:
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
display: flex;
min-height: 100vh;
}
/* The sidebar nav */
.sidebar {
width: 220px;
min-height: 100vh;
background-color: #2d2d2d;
flex-shrink: 0; /* sidebar never shrinks */
}
.sidebar ul {
list-style: none;
padding: 20px 0;
}
.sidebar li a {
display: block;
color: #ccc;
padding: 14px 24px;
text-decoration: none;
font-size: 15px;
transition: background-color 0.2s;
}
.sidebar li a:hover {
background-color: #444;
color: white;
}
.sidebar li a.active {
background-color: #e94560;
color: white;
}
/* Main content area */
.main-content {
flex: 1; /* takes all remaining space */
padding: 40px;
background-color: #f5f5f5;
}
.main-content h1 {
font-size: 28px;
color: #1a1a2e;
margin-bottom: 16px;
}
</style>
</head>
<body>
<nav class="sidebar">
<ul>
<li><a href="#" class="active">Dashboard</a></li>
<li><a href="#">Analytics</a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Logout</a></li>
</ul>
</nav>
<main class="main-content">
<h1>Welcome to the Dashboard</h1>
<p>Select an option from the sidebar to get started.</p>
</main>
</body>
</html>
Expected Output:
- A dark sidebar on the left with navigation links (white/grey text, red active state).
- A light grey main content area filling the rest of the screen.
- Both areas stretch to the full viewport height.
Part 5: Building a Horizontal Navigation Bar
A horizontal navbar runs across the top of the page with links side by side. This is the most common navbar style — used on almost every website header.
There are two main CSS techniques to make list items sit side by side:
display: inline-blockon the<li>elements- Flexbox on the
<ul>— the modern, recommended approach
Method 1: display: inline-block on <li>
<!DOCTYPE html>
<html>
<head>
<style>
ul.horiz-nav {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333;
overflow: hidden; /* clearfix-style safety net */
}
/* Make list items sit side by side */
ul.horiz-nav li {
display: inline-block; /* items sit in a horizontal row */
}
ul.horiz-nav li a {
display: block;
color: white;
padding: 14px 20px;
text-decoration: none;
font-size: 15px;
}
ul.horiz-nav li a:hover {
background-color: #555;
}
ul.horiz-nav li a.active {
background-color: #04aa6d;
}
</style>
</head>
<body>
<nav>
<ul class="horiz-nav">
<li><a href="#home" class="active">Home</a></li>
<li><a href="#news">News</a></li>
<li><a href="#contact">Contact</a></li>
<li><a href="#about">About</a></li>
</ul>
</nav>
</body>
</html>
Expected Output:
- A dark horizontal bar spanning the full page width.
- Four links side by side with comfortable padding.
- “Home” has a green background. Other links go dark grey on hover.
Why display: inline-block on <li> AND display: block on <a>?
display: inline-blockon<li>makes the list items sit side by side (instead of stacking vertically).display: blockon<a>makes the clickable area fill the entire padding box of the link — not just the text. This gives a larger, more comfortable click target.
Method 2: Flexbox on <ul> (Recommended)
ul.flex-nav {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333;
display: flex; /* makes children (li items) sit in a row */
}
ul.flex-nav li a {
display: block;
color: white;
padding: 14px 20px;
text-decoration: none;
}
ul.flex-nav li a:hover {
background-color: #555;
}
Using display: flex on the <ul> makes all its <li> children automatically sit side by side without needing to change the <li> display property at all.
Which method should you use? Flexbox is the modern, preferred approach. It handles spacing and alignment far more easily and works better for responsive design. Use
inline-blockonly when you need to support very old browsers or are working in older codebases.
Part 6: Right-Aligning a Navbar Link
Sometimes you need most links on the left but one link — like “Login” or “Sign Up” — pushed to the far right. Flexbox makes this trivial with margin-left: auto on the last item.
ul.flex-nav {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333;
display: flex;
}
ul.flex-nav li a {
display: block;
color: white;
padding: 14px 20px;
text-decoration: none;
}
ul.flex-nav li a:hover {
background-color: #555;
}
/* Push the last item to the far right */
ul.flex-nav li:last-child {
margin-left: auto;
}
<ul class="flex-nav">
<li><a href="#" class="active">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
<li><a href="#">Login</a></li> <!-- this gets pushed right -->
</ul>
Expected Output:
- “Home”, “About”, “Contact” are on the left.
- “Login” is pushed all the way to the right side of the bar.
How does margin-left: auto work here?
In a flex container, margin: auto absorbs all the available free space in the specified direction. Setting margin-left: auto on an item tells it to push itself as far right as possible by consuming all the leftover horizontal space.
Part 7: Adding a Logo to the Left Side
In a real website header, the logo sits on the left and the navigation links sit on the right (or follow the logo). Here is the full pattern:
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
.topnav {
background-color: #1a1a2e;
display: flex;
align-items: center; /* vertically centre all children */
padding: 0 30px;
}
.logo {
color: gold;
font-size: 22px;
font-weight: bold;
padding: 14px 0;
text-decoration: none;
margin-right: auto; /* push everything else to the right */
}
.nav-links {
list-style: none;
display: flex;
}
.nav-links li a {
display: block;
color: #ccc;
padding: 14px 18px;
text-decoration: none;
font-size: 15px;
transition: color 0.2s;
}
.nav-links li a:hover {
color: white;
}
.nav-links li a.active {
color: gold;
font-weight: bold;
}
.nav-links .btn-cta {
background-color: #e94560;
color: white !important;
border-radius: 20px;
padding: 8px 20px !important;
margin: 10px 0 10px 10px;
transition: background-color 0.2s !important;
}
.nav-links .btn-cta:hover {
background-color: #c73652 !important;
}
</style>
</head>
<body>
<header>
<nav class="topnav">
<a class="logo" href="#">BrandName</a>
<ul class="nav-links">
<li><a href="#" class="active">Home</a></li>
<li><a href="#">Features</a></li>
<li><a href="#">Pricing</a></li>
<li><a href="#">Blog</a></li>
<li><a href="#" class="btn-cta">Get Started</a></li>
</ul>
</nav>
</header>
</body>
</html>
Expected Output:
- A dark blue header bar.
- “BrandName” logo in gold on the far left.
- Nav links in grey on the right, with “Home” highlighted in gold.
- A red pill-shaped “Get Started” call-to-action button at the far right.
Part 8: Fixed/Sticky Navigation Bars
One of the most common requirements in modern web design is a navbar that stays at the top of the screen as the user scrolls down the page.
Fixed Navbar
A position: fixed navbar stays anchored to the top of the browser viewport at all times, regardless of scrolling.
.fixed-nav {
position: fixed; /* removes it from normal document flow */
top: 0; /* pins it to the very top */
left: 0; /* stretches from the left edge */
width: 100%; /* full width */
background-color: #333;
z-index: 1000; /* above all other page content */
}
Important: A fixed navbar is removed from the normal document flow. This means the content below it will slide underneath it. You must add
padding-top(equal to the navbar height) to the<body>or main content area to prevent content from being hidden behind the navbar.
body {
padding-top: 60px; /* matches the navbar height */
}
Sticky Navbar
A position: sticky navbar behaves like a normal element in the flow until the user scrolls past it — then it sticks to the top.
.sticky-nav {
position: sticky;
top: 0; /* stick to the very top when scrolled to */
background-color: #333;
z-index: 100;
}
Difference between fixed and sticky:
fixed: Always at the top, even when you first load the page. Content must be padded to account for it.sticky: Sits in normal flow at first, then sticks when scrolled past. No need to pad content.
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; }
.sticky-nav {
position: sticky;
top: 0;
background-color: #0f3460;
display: flex;
list-style: none;
padding: 0 20px;
z-index: 100;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.sticky-nav li a {
display: block;
color: white;
padding: 16px 18px;
text-decoration: none;
}
.sticky-nav li a:hover {
background-color: rgba(255,255,255,0.1);
}
.content-section {
padding: 60px 40px;
min-height: 100vh;
border-bottom: 1px solid #eee;
}
.content-section:nth-child(odd) {
background-color: #f9f9f9;
}
</style>
</head>
<body>
<ul class="sticky-nav">
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
</ul>
<div class="content-section" id="section1">
<h2>Section 1</h2>
<p>Scroll down to see the navbar stick to the top.</p>
</div>
<div class="content-section" id="section2">
<h2>Section 2</h2>
<p>The navbar is still visible at the top as you scroll.</p>
</div>
<div class="content-section" id="section3">
<h2>Section 3</h2>
<p>All three sections are reachable via the sticky navbar.</p>
</div>
</body>
</html>
Expected Output:
- Three tall page sections.
- A dark blue navbar that sits at the top normally when the page loads.
- As you scroll down, the navbar sticks to the top of the viewport and stays there.
Part 9: Adding a Dropdown Menu to a Horizontal Navbar
A dropdown menu appears when you hover over a navbar link, revealing sub-links below it. This is achieved entirely with CSS using position: absolute and :hover.
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
/* The main navbar */
ul.dropdown-nav {
list-style: none;
background-color: #333;
display: flex;
}
ul.dropdown-nav > li {
position: relative; /* anchor for the absolute dropdown */
}
ul.dropdown-nav > li > a {
display: block;
color: white;
padding: 14px 20px;
text-decoration: none;
}
ul.dropdown-nav > li > a:hover {
background-color: #555;
}
/* The dropdown container — hidden by default */
.dropdown-content {
display: none; /* hidden until hover */
position: absolute;
top: 100%; /* appears directly below the parent li */
left: 0;
background-color: #444;
min-width: 180px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 1;
}
/* Each dropdown link */
.dropdown-content a {
display: block;
color: white;
padding: 12px 18px;
text-decoration: none;
font-size: 14px;
}
.dropdown-content a:hover {
background-color: #666;
}
/* Show dropdown when hovering the parent li */
ul.dropdown-nav > li:hover .dropdown-content {
display: block;
}
</style>
</head>
<body>
<nav>
<ul class="dropdown-nav">
<li><a href="#">Home</a></li>
<li>
<a href="#">Services ▾</a> <!-- ▾ is a small downward triangle indicator -->
<div class="dropdown-content">
<a href="#">Web Design</a>
<a href="#">Development</a>
<a href="#">Hosting</a>
</div>
</li>
<li>
<a href="#">Products ▾</a>
<div class="dropdown-content">
<a href="#">Software</a>
<a href="#">Hardware</a>
<a href="#">Support</a>
</div>
</li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</body>
</html>
Expected Output:
- A dark horizontal navbar with four items.
- “Home” and “Contact” are plain links.
- Hovering over “Services” reveals a dropdown with three sub-links.
- Hovering over “Products” reveals another dropdown with three sub-links.
- Dropdowns disappear when you move the mouse away.
How the dropdown works — step by step:
.dropdown-contentis hidden by default (display: none).- The parent
<li>hasposition: relative— this makes it the anchor for the dropdown’s absolute positioning. .dropdown-contenthasposition: absolutewithtop: 100%— this places it directly below the parent<li>.ul.dropdown-nav > li:hover .dropdown-content— when the user hovers over the parent<li>, this selector appliesdisplay: blockto the dropdown, making it visible.- Because the mouse moves from the
<li>into the.dropdown-content(which is a child of<li>), the hover state is maintained and the dropdown stays open.
Thinking Prompt: What happens to the dropdown if you remove
position: relativefrom the<li>? (The dropdown loses its anchor and positions itself relative to the nearest positioned ancestor — possibly the whole page — causing it to appear in the wrong place.)
Part 10: Styling a Full Horizontal Navbar — Variations and Options
Adding a Bottom Border/Underline on Active Link
.nav-links li a.active {
border-bottom: 3px solid gold; /* underline style active indicator */
color: white;
}
Adding a Divider Between Links
ul.horiz-nav li {
border-right: 1px solid #555; /* vertical divider line on the right */
}
ul.horiz-nav li:last-child {
border-right: none; /* remove from last item */
}
Making the Active Link Stand Out with a Top Border
.nav-links li a.active {
border-top: 3px solid #e94560; /* coloured top line for active page */
padding-top: 11px; /* compensate for the 3px border */
}
Adding a Background Colour Change on the Entire Navbar on Scroll (using a class)
You can add a class with JavaScript to change the navbar’s appearance once the user has scrolled down. In pure CSS, you can pre-style that class:
.scrolled {
background-color: rgba(0, 0, 0, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
}
Part 11: Guided Practice Exercises
Exercise 1 — Build a Basic Vertical Navbar (Warm-Up)
Objective: Transform a plain list into a styled vertical sidebar navigation.
Scenario: You are building a dashboard sidebar for a project management app.
Starter Code:
<!DOCTYPE html>
<html>
<head>
<style>
/* Your CSS goes here */
</style>
</head>
<body>
<nav>
<ul>
<li><a href="#">Dashboard</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Tasks</a></li>
<li><a href="#">Team</a></li>
<li><a href="#">Settings</a></li>
</ul>
</nav>
</body>
</html>
Your Task — Add CSS to achieve:
- Remove bullets and default spacing from
ul - Set
ulwidth to240pxand background to#1e293b(dark navy) - Make each
<a>display: block, colour#94a3b8(grey), padding13px 20px, no underline - On hover: background
#334155, colourwhite - Add
class="active"to “Dashboard” and style it: background#3b82f6(blue), colourwhite
Expected Output: A dark navy sidebar with “Dashboard” highlighted in blue and the rest styled as grey text that turns white on hover.
Solution:
ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 240px;
background-color: #1e293b;
}
ul li a {
display: block;
color: #94a3b8;
padding: 13px 20px;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
}
ul li a:hover {
background-color: #334155;
color: white;
}
ul li a.active {
background-color: #3b82f6;
color: white;
}
Self-Check: Remove display: block from li a. What changes? (Clicking the link only works when you click the text itself — the padding area is no longer clickable. This demonstrates why display: block is essential.)
Exercise 2 — Build a Horizontal Navbar with a Right-Aligned Button
Objective: Create a top navigation bar with a logo on the left and links + a CTA button on the right.
Your Task:
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
/* Style the header to use Flexbox, dark background, align items vertically */
.header { /* your styles */ }
/* Style the logo: gold colour, large font, no underline */
.logo { /* your styles */ }
/* Style the ul: no list style, display flex */
.nav-list { /* your styles */ }
/* Style each link: white text, padding, no underline, hover darkens */
.nav-list li a { /* your styles */ }
/* Style the .cta button: red background, white text, rounded corners */
.cta { /* your styles */ }
</style>
</head>
<body>
<header class="header">
<a class="logo" href="#">ShopNow</a>
<ul class="nav-list">
<li><a href="#">Home</a></li>
<li><a href="#">Shop</a></li>
<li><a href="#">Deals</a></li>
<li><a href="#" class="cta">Sign Up Free</a></li>
</ul>
</header>
</body>
</html>
Expected Output:
- Dark header with “ShopNow” logo on the left in gold.
- “Home”, “Shop”, “Deals” links in white on the right.
- “Sign Up Free” button with red background and rounded corners.
Solution:
.header {
background-color: #1a1a2e;
display: flex;
align-items: center;
padding: 0 30px;
}
.logo {
color: gold;
font-size: 22px;
font-weight: bold;
text-decoration: none;
padding: 14px 0;
margin-right: auto; /* pushes everything else to the right */
}
.nav-list {
list-style: none;
display: flex;
align-items: center;
gap: 5px;
}
.nav-list li a {
display: block;
color: white;
padding: 10px 16px;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.2s;
}
.nav-list li a:hover {
background-color: rgba(255,255,255,0.1);
}
.cta {
background-color: #e94560;
color: white !important;
border-radius: 20px;
padding: 9px 20px !important;
}
.cta:hover {
background-color: #c73652 !important;
}
Exercise 3 — Dropdown Menu Challenge
Objective: Add a dropdown submenu to one link in a horizontal navbar.
Your Task: Take the horizontal navbar from Exercise 2 and add a “Categories” link that has a dropdown with: Electronics, Clothing, and Home & Garden.
Hint — Key steps:
- Add a new
<li>for “Categories” with a nested<div class="dropdown-content">inside it. - Give the parent
<li>position: relative. - Give
.dropdown-contentdisplay: none,position: absolute,top: 100%, a dark background, andmin-width: 160px. - Add
li:hover .dropdown-content { display: block; }.
Expected Output:
- Hovering over “Categories” reveals a dropdown with three items.
- The dropdown has a dark background with white text.
- Moving the mouse away hides the dropdown.
Exercise 4 — Sticky Navbar with Scroll Effect
Objective: Build a page with a sticky navbar and three full-height sections.
Your Task:
- Create a
<ul>navbar withposition: sticky; top: 0. - Create three
<section>elements each withheight: 100vhand different background colours. - Each navbar link should point to a section using
href="#section1"etc. andid="section1"on the sections.
Expected Output:
- The page initially shows the navbar at the top with the first section below.
- As you scroll, the navbar stays pinned to the top of the viewport.
- Clicking a nav link jumps to that section.
Part 12: Mini Project — Complete Website Header
In this project you will build a full, production-quality website header that includes a logo, horizontal navigation, a sticky behaviour, and a dropdown menu — everything you have learned combined.
Project HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TechFlow — Home</title>
<link rel="stylesheet" href="header.css">
</head>
<body>
<!-- STICKY HEADER -->
<header class="site-header">
<nav class="navbar">
<!-- Logo -->
<a class="nav-logo" href="#">⚡ TechFlow</a>
<!-- Navigation Links -->
<ul class="nav-menu">
<li><a href="#home" class="active">Home</a></li>
<!-- Dropdown: Solutions -->
<li class="has-dropdown">
<a href="#">Solutions ▾</a>
<ul class="dropdown">
<li><a href="#">Web Development</a></li>
<li><a href="#">Mobile Apps</a></li>
<li><a href="#">Cloud Hosting</a></li>
<li><a href="#">Data Analytics</a></li>
</ul>
</li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#blog">Blog</a></li>
<li><a href="#about">About</a></li>
</ul>
<!-- CTA Buttons -->
<div class="nav-cta">
<a href="#login" class="btn-ghost">Log In</a>
<a href="#signup" class="btn-primary">Start Free Trial</a>
</div>
</nav>
</header>
<!-- HERO SECTION (to demonstrate sticky navbar) -->
<main>
<section class="hero" id="home">
<div class="hero-content">
<h1>Build Faster.<br>Ship Smarter.</h1>
<p>The all-in-one platform for modern development teams.</p>
<a href="#" class="hero-btn">Get Started — It's Free</a>
</div>
</section>
<section class="section-alt" id="pricing">
<h2>Pricing</h2>
<p>Scroll up to see the sticky navbar in action.</p>
</section>
<section class="section-plain" id="blog">
<h2>Blog</h2>
<p>Latest articles and tutorials.</p>
</section>
<section class="section-alt" id="about">
<h2>About Us</h2>
<p>Building tools developers love since 2019.</p>
</section>
</main>
</body>
</html>
Project CSS
/* header.css */
/* =====================
RESET & BASE
===================== */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Arial, sans-serif;
color: #1a1a2e;
}
/* =====================
STICKY SITE HEADER
===================== */
.site-header {
position: sticky;
top: 0;
z-index: 1000;
background-color: #0f0f1a;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}
/* =====================
NAVBAR LAYOUT
===================== */
.navbar {
display: flex;
align-items: center;
max-width: 1200px;
margin: 0 auto; /* centres the navbar content on wide screens */
padding: 0 30px;
height: 64px; /* fixed navbar height */
}
/* =====================
LOGO
===================== */
.nav-logo {
color: #7c3aed; /* purple */
font-size: 22px;
font-weight: 800;
text-decoration: none;
letter-spacing: -0.5px;
margin-right: auto; /* pushes menu and CTA to the right */
white-space: nowrap;
}
/* =====================
NAV MENU (ul)
===================== */
.nav-menu {
list-style: none;
display: flex;
align-items: center;
gap: 2px;
}
.nav-menu > li {
position: relative; /* anchor for dropdown */
}
.nav-menu > li > a {
display: block;
color: #cbd5e1;
padding: 8px 14px;
text-decoration: none;
font-size: 14px;
font-weight: 500;
border-radius: 6px;
transition: background-color 0.2s, color 0.2s;
white-space: nowrap;
}
.nav-menu > li > a:hover {
background-color: rgba(255, 255, 255, 0.07);
color: white;
}
.nav-menu > li > a.active {
color: white;
background-color: rgba(124, 58, 237, 0.2); /* subtle purple glow */
}
/* =====================
DROPDOWN MENU
===================== */
.dropdown {
display: none; /* hidden by default */
position: absolute;
top: calc(100% + 8px); /* 8px gap below the parent link */
left: 0;
background-color: #1e1e30;
border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px;
min-width: 200px;
padding: 8px 0;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
list-style: none;
z-index: 100;
}
.dropdown li a {
display: block;
color: #94a3b8;
padding: 10px 18px;
text-decoration: none;
font-size: 14px;
transition: background-color 0.15s, color 0.15s;
}
.dropdown li a:hover {
background-color: rgba(124,58,237,0.15);
color: white;
}
/* Show dropdown when parent li is hovered */
.has-dropdown:hover .dropdown {
display: block;
}
/* =====================
CTA BUTTONS
===================== */
.nav-cta {
display: flex;
align-items: center;
gap: 10px;
margin-left: 20px;
}
.btn-ghost {
color: #94a3b8;
padding: 7px 16px;
text-decoration: none;
font-size: 14px;
font-weight: 500;
border: 1px solid rgba(255,255,255,0.12);
border-radius: 8px;
transition: border-color 0.2s, color 0.2s;
white-space: nowrap;
}
.btn-ghost:hover {
border-color: rgba(255,255,255,0.3);
color: white;
}
.btn-primary {
background-color: #7c3aed;
color: white;
padding: 8px 18px;
text-decoration: none;
font-size: 14px;
font-weight: 600;
border-radius: 8px;
transition: background-color 0.2s, transform 0.1s;
white-space: nowrap;
}
.btn-primary:hover {
background-color: #6d28d9;
transform: translateY(-1px);
}
/* =====================
HERO SECTION
===================== */
.hero {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #0f0f1a 0%, #1a0533 50%, #0f0f1a 100%);
text-align: center;
padding: 40px 20px;
}
.hero-content h1 {
font-size: 56px;
font-weight: 900;
color: white;
line-height: 1.1;
margin-bottom: 20px;
}
.hero-content p {
font-size: 20px;
color: #94a3b8;
margin-bottom: 36px;
max-width: 500px;
}
.hero-btn {
display: inline-block;
background-color: #7c3aed;
color: white;
padding: 16px 36px;
border-radius: 10px;
text-decoration: none;
font-size: 16px;
font-weight: 700;
transition: background-color 0.2s, transform 0.2s;
}
.hero-btn:hover {
background-color: #6d28d9;
transform: translateY(-2px);
}
/* =====================
OTHER SECTIONS
===================== */
.section-alt {
min-height: 80vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f8fafc;
gap: 16px;
}
.section-plain {
min-height: 80vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #fff;
gap: 16px;
}
.section-alt h2,
.section-plain h2 {
font-size: 32px;
color: #1a1a2e;
}
.section-alt p,
.section-plain p {
color: #64748b;
}
Project Milestone Outputs
Stage 1 (HTML only): Plain unstyled text, bulleted list, bare links.
Stage 2 (After navbar styles): Dark sticky header with logo on the left, navigation links in the centre-right, and two buttons at the far right.
Stage 3 (After dropdown): Hovering over “Solutions” reveals a card-style dropdown with four sub-links.
Stage 4 (After hero section): A full-screen dark gradient hero section with large heading and a purple “Get Started” button.
Stage 5 (Complete): Scrolling down through the four sections while the navbar stays fixed at the top. The “Home” link has an active purple tint.
Project Reflection Questions
-
The
.navbarhasmax-width: 1200pxandmargin: 0 auto. What is the purpose of these two properties together? (They ensure that on very large screens, the navbar content doesn’t stretch to the full screen width — it stays centred and readable at 1200px max.) -
Why does
.nav-logohavemargin-right: auto? What would happen if you removed it? (It pushes the menu and CTA buttons to the right. Without it, the logo, links, and buttons would all cluster together on the left.) -
The
.has-dropdownusesli:hover .dropdown. Could you do this with CSS transitions to animate the dropdown appearing? How? (You could removedisplay: none/blockand instead useopacity: 0 → 1+visibility: hidden → visible+ atransition: opacityto fade it in.displaycannot be transitioned directly.) -
The
.site-headerhasposition: sticky; top: 0. Why does it also needz-index: 1000? (Without a z-index, the sticky header would be covered by content sections as you scroll, because those sections would naturally appear on top of it in the paint order.) -
What single CSS change would convert this horizontal header into a vertical sidebar? (Change the
.navbartoflex-direction: column, adjust widths and heights, and possibly change the dropdown to open to the right instead of downwards.)
Part 13: Common Beginner Mistakes
Mistake 1 — Forgetting display: block on <a> tags
Wrong:
li a {
color: white;
padding: 12px 20px; /* padding applies only to text area — hard to click */
text-decoration: none;
}
Correct:
li a {
display: block; /* the ENTIRE padded area becomes clickable */
color: white;
padding: 12px 20px;
text-decoration: none;
}
Mistake 2 — Forgetting to reset list styles
Wrong (using raw <ul> without reset):
The navbar shows bullet points and left indentation — it looks completely broken.
Correct:
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
Mistake 3 — Fixed navbar hiding page content
/* The navbar is fixed, but content starts at the very top — hidden underneath! */
.navbar { position: fixed; top: 0; }
/* MISSING: compensate for the navbar height */
body { padding-top: 70px; } /* add this — matches the navbar height */
Mistake 4 — Dropdown disappears before you can click it
The problem: Dropdown disappears because there is a gap between the nav link and the dropdown, and hovering the gap triggers mouseleave.
Cause: top: 100% with a margin between the trigger and the dropdown.
Fix: Remove the gap entirely or use top: calc(100% + 8px) but also extend the parent’s hover area to cover the gap with a transparent ::after pseudo-element, or simply use top: 100% without a gap.
/* Simple fix — no gap */
.dropdown {
top: 100%; /* no gap — the dropdown immediately connects to the trigger */
}
Mistake 5 — Dropdown appearing behind other page content
/* Dropdown is invisible because other elements are rendering above it */
.has-dropdown {
position: relative;
/* Missing z-index on the dropdown! */
}
.dropdown {
position: absolute;
z-index: 100; /* ADD THIS — must be high enough to sit above page content */
}
Mistake 6 — Using float for horizontal nav and forgetting overflow: hidden on the parent
/* li items are floated and the parent ul collapses to zero height */
ul {
background-color: #333;
/* Missing: overflow: hidden; or a clearfix */
}
li { float: left; }
Fix:
ul {
background-color: #333;
overflow: hidden; /* or overflow: auto — forces ul to contain its floated children */
}
Reflection Questions
-
Why is a
<ul>list the correct HTML element to use as the foundation of a navbar? What would be wrong with using a<div>with<span>children instead? -
You have a vertical navbar and the links are very hard to click — you have to click exactly on the tiny text. What single CSS property fixes this? (
display: blockon the<a>tags.) -
A horizontal navbar has all its links clustered on the left, but you want the last link (“Sign Up”) pushed to the far right. You are using Flexbox on the
<ul>. What is the simplest CSS fix? (margin-left: autoon the last<li>.) -
What is the difference between
position: fixedandposition: stickyfor a navbar? When would you choose one over the other? -
A dropdown menu works when you hover over it, but disappears the instant you move the mouse from the trigger link to the dropdown. What causes this and how do you fix it? (A gap between the link and the dropdown. Remove the gap so the dropdown directly connects to the trigger, maintaining the hover state.)
-
You add a sticky navbar to a page, but it does not appear to stick. What are two possible reasons? (The parent element might have
overflow: hiddenwhich prevents sticky from working; or the navbar’s parent container does not have a defined height to scroll within.)
Completion Checklist
- I understand why a
<ul>list is the correct HTML structure for a navbar - I can remove default list styles (bullets, margin, padding) as a starting point
- I understand why
display: blockon<a>tags is essential for proper click areas - I can build a complete vertical navbar with hover and active states
- I can place a vertical navbar next to main content using Flexbox
- I can build a horizontal navbar using both
display: inline-blockand Flexbox methods - I know when to use Flexbox vs
inline-blockfor horizontal navbars - I can push a single nav item (like a “Login” button) to the far right using
margin-left: auto - I can add a logo to the left and links to the right in a header layout
- I understand the difference between
position: fixedandposition: stickynavbars - I can build a dropdown submenu using
position: absoluteand:hover - I know why
position: relativeon the parent<li>is required for dropdowns - I know why
z-indexis needed on dropdowns and sticky navbars - I completed all four guided exercises
- I built the complete TechFlow website header mini-project
- I can identify and fix the six common beginner navbar mistakes
Lesson Summary
A CSS navigation bar always starts the same way: a semantic <nav> element containing a <ul> list of <li> items, each holding an <a> link. The first CSS task is always to reset the default list styles with list-style-type: none, margin: 0, and padding: 0. The second task is always to set display: block on the <a> tags so the full padded area is clickable.
Vertical navbars stack links top to bottom and are ideal for dashboards and sidebars. The key properties are a fixed width on the <ul>, display: block on links, and Flexbox on the page body to place the sidebar alongside content.
Horizontal navbars place links side by side and are ideal for website headers. The cleanest modern approach is display: flex on the <ul>. A logo is placed before the <ul> in the flex container with margin-right: auto to push the links right. Individual links are pushed to the far right using margin-left: auto on a specific <li>.
Dropdown menus are built by nesting a second <ul> or <div> inside a <li>, setting display: none by default and display: block on the parent’s :hover. The parent <li> needs position: relative and the dropdown needs position: absolute, top: 100%, and a z-index to sit above page content.
Sticky navbars use position: sticky; top: 0 and need z-index to stay above all page content. Fixed navbars use position: fixed; top: 0 and require padding-top on the body to prevent content hiding behind the navbar.
Real World Uses of CSS Navbars:
- Top horizontal header on almost every website
- Sidebar navigation in dashboards (Notion, Trello, Figma)
- Sticky headers that reveal on scroll on news sites and blogs
- Dropdown mega-menus on e-commerce sites (Amazon, Jumia)
- Mobile hamburger menus (built by toggling a class on the navbar)
- Tab-bar style horizontal navs in web applications
- Breadcrumb navigation showing the current page path