Lesson 17: CSS Lists — Styling Unordered and Ordered Lists
Lesson 17: CSS Lists — Styling Unordered and Ordered Lists
Lesson Introduction
Lists are everywhere on the web. Every navigation menu, every step-by-step guide, every bullet-pointed feature comparison — all of these are built with HTML lists. By default, browser lists are plain: black dots for bullets, plain numbers for ordered steps, and a fixed amount of indentation.
But with CSS, you can transform lists completely. You can change the bullet to a square, a circle, a Roman numeral, or even a custom image. You can remove the bullet entirely and use the list as a layout tool. You can add colours, borders, hover effects, and padding to turn a plain list into a polished feature section or a styled navigation menu.
This lesson covers everything from the W3Schools CSS Styling Lists tutorial and its code challenges, merged into one smooth, progressive lesson that starts from absolute zero and ends with a real-world styled project.
Prerequisite Concepts
What is an HTML List?
There are two main types of lists in HTML:
1. Unordered List (<ul>) — items without a specific order. Each item gets a bullet point by default.
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Expected output in browser:
• Apples
• Bananas
• Cherries
2. Ordered List (<ol>) — items with a specific order. Each item gets a number by default.
<ol>
<li>Preheat oven to 180°C</li>
<li>Mix the ingredients</li>
<li>Bake for 30 minutes</li>
</ol>
Expected output in browser:
1. Preheat oven to 180°C
2. Mix the ingredients
3. Bake for 30 minutes
The <li> tag stands for “list item.” Every item inside a <ul> or <ol> must be wrapped in <li> tags.
Real-world analogy: A
<ul>is like a shopping list — the order doesn’t matter. A<ol>is like a recipe — the order absolutely matters.
What Properties Does CSS Give Us for Lists?
CSS provides three dedicated list-styling properties:
| Property | What It Controls |
|---|---|
list-style-type |
The shape or style of the bullet or number |
list-style-image |
Replaces the bullet with a custom image |
list-style-position |
Whether the bullet sits inside or outside the list item box |
Plus a shorthand: list-style — which sets all three in one line.
We will explore each one thoroughly.
Part 1 — list-style-type: Changing the Bullet or Number Style
What is list-style-type?
list-style-type is the CSS property that controls what kind of marker appears before each list item. The “marker” is the bullet dot, number, letter, or symbol at the start of each <li>.
Why does this matter? Different contexts call for different visual styles. A recipe needs numbers. A feature list needs clean bullets. A sidebar menu needs no bullets at all. CSS gives you full control over which marker is used.
Values for Unordered Lists (<ul>)
disc — Filled Circle (Default)
This is the browser’s default bullet. It is a solid, filled circle.
<ul style="list-style-type: disc;">
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
Expected output:
● HTML
● CSS
● JavaScript
circle — Hollow Circle
An outline circle — not filled, just the ring.
ul {
list-style-type: circle;
}
<ul>
<li>Python</li>
<li>Java</li>
<li>C++</li>
</ul>
Expected output:
○ Python
○ Java
○ C++
Thinking prompt: When would a hollow circle look better than a filled disc? Think about light backgrounds vs dark backgrounds.
square — Filled Square
A solid filled square. Popular in modern, structured layouts.
ul {
list-style-type: square;
}
Expected output:
■ Item One
■ Item Two
■ Item Three
none — No Bullet at All
This completely removes the bullet marker. It is one of the most useful values because it lets you use <ul> purely as a layout container without any visual marker.
ul {
list-style-type: none;
}
Expected output:
Item One
Item Two
Item Three
No bullet at all — just the text. This is used extensively for navigation menus, card lists, and anywhere a list structure is needed without the visual marker.
Values for Ordered Lists (<ol>)
decimal — Numbers (Default)
The default for ordered lists. 1, 2, 3, 4…
ol {
list-style-type: decimal;
}
Expected output:
1. Step One
2. Step Two
3. Step Three
decimal-leading-zero — Numbers with Leading Zero
Good for lists that need zero-padded numbers: 01, 02, 03…
ol {
list-style-type: decimal-leading-zero;
}
Expected output:
01. Step One
02. Step Two
03. Step Three
lower-roman — Lowercase Roman Numerals
Uses i, ii, iii, iv, v… Great for outlines, legal documents, or academic formatting.
ol {
list-style-type: lower-roman;
}
Expected output:
i. Chapter One
ii. Chapter Two
iii. Chapter Three
upper-roman — Uppercase Roman Numerals
Uses I, II, III, IV, V… More formal than lowercase.
ol {
list-style-type: upper-roman;
}
Expected output:
I. Introduction
II. Methodology
III. Results
lower-alpha — Lowercase Letters
Uses a, b, c, d… Excellent for sub-lists or multiple choice questions.
ol {
list-style-type: lower-alpha;
}
Expected output:
a. Option One
b. Option Two
c. Option Three
upper-alpha — Uppercase Letters
Uses A, B, C, D… Often used in formal outlines.
ol {
list-style-type: upper-alpha;
}
Expected output:
A. First Point
B. Second Point
C. Third Point
lower-greek — Lowercase Greek Letters
Uses α, β, γ… Useful in scientific or mathematical contexts.
ol {
list-style-type: lower-greek;
}
none — No Number Marker
Just like with <ul>, you can strip the number entirely from an ordered list:
ol {
list-style-type: none;
}
Side-by-Side Comparison — All Major Types
Here is a complete demo showing all major types in one file:
<!DOCTYPE html>
<html>
<head>
<style>
.disc { list-style-type: disc; }
.circle { list-style-type: circle; }
.square { list-style-type: square; }
.none { list-style-type: none; }
.decimal { list-style-type: decimal; }
.roman-l { list-style-type: lower-roman; }
.roman-u { list-style-type: upper-roman; }
.alpha-l { list-style-type: lower-alpha; }
.alpha-u { list-style-type: upper-alpha; }
</style>
</head>
<body>
<h3>Unordered</h3>
<ul class="disc"> <li>disc</li> <li>disc</li> </ul>
<ul class="circle"> <li>circle</li> <li>circle</li> </ul>
<ul class="square"> <li>square</li> <li>square</li> </ul>
<ul class="none"> <li>none</li> <li>none</li> </ul>
<h3>Ordered</h3>
<ol class="decimal"> <li>decimal</li> <li>decimal</li> </ol>
<ol class="roman-l"> <li>lower-roman</li> <li>lower-roman</li> </ol>
<ol class="roman-u"> <li>upper-roman</li> <li>upper-roman</li> </ol>
<ol class="alpha-l"> <li>lower-alpha</li> <li>lower-alpha</li> </ol>
<ol class="alpha-u"> <li>upper-alpha</li> <li>upper-alpha</li> </ol>
</body>
</html>
Expected output: A clear visual comparison of all list-style-type values rendered side by side.
Part 2 — list-style-image: Using a Custom Image as a Bullet
What is list-style-image?
Instead of a shape (disc, square, circle), you can use any image file as the bullet point. This is done with the list-style-image property.
Why would you use this? Brand consistency, visual flair, or themed websites. For example, a cooking website might use a small chef’s hat icon as a bullet, or a travel blog might use a small plane icon.
Syntax
ul {
list-style-image: url("path/to/image.png");
}
url(...)— tells CSS this is a file path or URL- The path inside the quotes points to your image file
Full Example
<!DOCTYPE html>
<html>
<head>
<style>
ul.custom-bullets {
list-style-image: url("https://www.w3schools.com/css/sqpurple.gif");
}
</style>
</head>
<body>
<ul class="custom-bullets">
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
</body>
</html>
Expected output: Each list item has a small purple square image as its bullet instead of the default disc.
Important note:
list-style-imagegives you limited control over the size and position of the image. For more precise control, designers often prefer removing the bullet withlist-style-type: noneand then adding the image using the CSSbackground-imageproperty on the<li>element, withpadding-leftto make room for it. We will see this technique in the exercises.
Part 3 — list-style-position: Inside vs Outside
What is list-style-position?
This property controls where the bullet marker is positioned relative to the list item’s content box. It has two values: outside (the default) and inside.
outside — Default Behaviour
The bullet marker sits outside the list item’s text box. This means if the text wraps to a second line, the wrapped text aligns back to the left edge of the first line — the bullet “hangs” to the left of the text.
ul {
list-style-position: outside;
}
Visual representation:
● This is a long list item text that wraps
to the next line and aligns with the
start of the first line of text.
The bullet hangs outside, and wrapped text indents inward.
inside — Bullet Inside the Content Box
The bullet marker is placed inside the list item’s content area. Wrapped lines of text align all the way to the left edge of the container — the same left edge where the bullet sits.
ul {
list-style-position: inside;
}
Visual representation:
● This is a long list item text that wraps
to the next line and goes all the way to
the left edge, under the bullet.
Comparing Both Side by Side
<!DOCTYPE html>
<html>
<head>
<style>
ul.outside-list {
list-style-position: outside;
background-color: #f9f9f9;
border: 1px solid #ccc;
padding: 10px 40px;
}
ul.inside-list {
list-style-position: inside;
background-color: #f0f8ff;
border: 1px solid #9bc;
padding: 10px 20px;
}
</style>
</head>
<body>
<h3>Outside (default)</h3>
<ul class="outside-list">
<li>This is a list item with enough text to wrap onto the next line so you can see the difference clearly</li>
<li>Short item</li>
</ul>
<h3>Inside</h3>
<ul class="inside-list">
<li>This is a list item with enough text to wrap onto the next line so you can see the difference clearly</li>
<li>Short item</li>
</ul>
</body>
</html>
Expected output: You can clearly see the text wrapping difference. With outside, wrapped text indents away from the left. With inside, wrapped text aligns with the very left edge of the container.
When to use
inside: When you want a compact, aligned look, especially for list items inside cards, sidebar panels, or text-heavy content blocks where indentation would feel excessive.
Part 4 — list-style Shorthand
What is the Shorthand?
Instead of writing three separate properties, you can combine list-style-type, list-style-position, and list-style-image into one line with the list-style shorthand.
Syntax
list-style: [type] [position] [image];
All three values are optional. You only need to include the ones you want to set.
Examples
Set type and position only:
ul {
list-style: square inside;
}
This is equivalent to:
ul {
list-style-type: square;
list-style-position: inside;
/* list-style-image defaults to none */
}
Set type only:
ol {
list-style: upper-roman;
}
Equivalent to:
ol {
list-style-type: upper-roman;
}
Remove all markers:
ul {
list-style: none;
}
This is the most common shorthand usage — stripping all list styling in one line.
Set image and position:
ul {
list-style: url("bullet.png") inside;
}
Full Demo
<!DOCTYPE html>
<html>
<head>
<style>
ul.features {
list-style: square inside;
color: #333;
font-size: 16px;
line-height: 1.8;
}
ol.steps {
list-style: upper-roman outside;
color: #1a237e;
font-size: 16px;
line-height: 1.8;
}
</style>
</head>
<body>
<h3>Product Features</h3>
<ul class="features">
<li>Lightning fast performance</li>
<li>Works on all devices</li>
<li>Free to use forever</li>
</ul>
<h3>Setup Steps</h3>
<ol class="steps">
<li>Download the installer</li>
<li>Run the setup wizard</li>
<li>Launch the application</li>
</ol>
</body>
</html>
Expected output:
- Features list: square bullets positioned inside, dark grey text
- Steps list: uppercase Roman numerals (I, II, III) positioned outside, dark navy text
Part 5 — Removing Default List Spacing and Padding
Why Remove Default Spacing?
Browsers add default padding-left and sometimes margin to <ul> and <ol> elements. This is what creates the indentation you always see in browser lists. When you are building a navigation menu or a styled card list, this default indentation gets in the way.
The Browser Default (What You Are Fighting)
/* Browser adds this automatically — you cannot see it but it is there */
ul, ol {
padding-left: 40px;
margin-top: 1em;
margin-bottom: 1em;
}
How to Remove It
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
Line by line:
list-style-type: none;— removes the bullet markersmargin: 0;— removes the vertical space above and below the whole listpadding: 0;— removes the left indentation
Expected output: The list items appear flush to the left edge of their container, with no bullets and no extra spacing.
This is the foundation of every CSS navigation menu. Once you remove the default list styling, you have a clean, flat group of
<li>elements you can style however you want.
Part 6 — Styling List Items: Colour, Background, and Padding
CSS treats <li> as a block-level element — meaning you can apply background colours, padding, margins, and borders directly to it. This is how you build styled lists that look like feature tables, settings menus, or product detail panels.
Adding Colour and Background to List Items
<!DOCTYPE html>
<html>
<head>
<style>
ul.coloured-list {
list-style-type: none;
padding: 0;
margin: 0;
width: 220px;
}
ul.coloured-list li {
background-color: #4CAF50;
color: white;
padding: 10px 16px;
margin-bottom: 4px;
}
</style>
</head>
<body>
<ul class="coloured-list">
<li>Dashboard</li>
<li>Reports</li>
<li>Settings</li>
<li>Log Out</li>
</ul>
</body>
</html>
Line by line:
ul.coloured-list li— targets every<li>inside a<ul>with classcoloured-listbackground-color: #4CAF50;— gives each item a green backgroundcolor: white;— white text for contrastpadding: 10px 16px;— breathing room inside each item (10px top/bottom, 16px left/right)margin-bottom: 4px;— a small gap between each item
Expected output: A vertical stack of green pills with white text — resembling a sidebar navigation menu.
Adding a Border Separator Between Items
Instead of a margin gap, you can use a bottom border to visually separate items:
ul.bordered-list li {
padding: 12px 16px;
border-bottom: 1px solid #ccc;
background-color: white;
}
ul.bordered-list li:last-child {
border-bottom: none; /* Remove border from the very last item */
}
New here: li:last-child is a CSS pseudo-class that selects only the LAST <li> in the list. We remove its bottom border because it is at the bottom of the list and does not need a separator.
Expected output: A clean list with thin grey lines dividing each item — similar to a settings menu or inbox list.
Full Styled Vertical List
<!DOCTYPE html>
<html>
<head>
<style>
ul.menu {
list-style-type: none;
padding: 0;
margin: 0;
width: 200px;
border: 1px solid #ddd;
border-radius: 6px;
overflow: hidden; /* clips rounded corners at items */
}
ul.menu li a {
display: block; /* makes the whole row clickable */
padding: 12px 16px;
text-decoration: none;
color: #333;
background-color: #f9f9f9;
border-bottom: 1px solid #ddd;
transition: background-color 0.2s ease;
}
ul.menu li:last-child a {
border-bottom: none;
}
ul.menu li a:hover {
background-color: #e3f2fd;
color: #0d47a1;
}
</style>
</head>
<body>
<ul class="menu">
<li><a href="#">Home</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Sign Out</a></li>
</ul>
</body>
</html>
New things here:
display: block;on the<a>inside<li>— this makes the entire row (not just the text) clickable. Without this, only the text acts as a clickable area.overflow: hidden;on the<ul>— ensures the rounded corners of the border are not broken by the background colour of the first and last items.transition: background-color 0.2s ease;— smooth hover colour change.
Expected output: A polished, bordered vertical navigation panel. Each row changes to a soft blue background on hover. The whole row is clickable.
Thinking prompt: What happens if you remove
display: blockfrom the<a>tag? The clickable area shrinks to only the text itself. Try it!
Part 7 — Horizontal Lists
Lists are vertical by default. But with CSS, you can arrange list items horizontally — which is exactly how most horizontal navigation bars and breadcrumbs are built.
Making a Horizontal List with display: inline
ul.horizontal {
list-style-type: none;
margin: 0;
padding: 0;
}
ul.horizontal li {
display: inline;
margin-right: 20px;
}
display: inline removes the block-level stacking behaviour of <li>. Items now flow side by side like text.
<ul class="horizontal">
<li>Home</li>
<li>About</li>
<li>Services</li>
<li>Contact</li>
</ul>
Expected output: The four items appear on one line, spaced 20px apart.
Making a Horizontal List with display: inline-block
inline-block is usually preferred over inline for list items because it lets you apply padding and height:
ul.navbar {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333;
overflow: hidden;
}
ul.navbar li {
display: inline-block;
}
ul.navbar li a {
display: block;
color: white;
text-decoration: none;
padding: 14px 20px;
transition: background-color 0.2s ease;
}
ul.navbar li a:hover {
background-color: #555;
}
<ul class="navbar">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
Expected output: A dark horizontal navigation bar with white text links. Each link’s background darkens on hover.
Why
display: blockon the<a>insidedisplay: inline-blockon<li>?
The<li>isinline-block, which flows horizontally. The<a>inside isblock, which fills the entire<li>so the click area covers the full padded rectangle — not just the text.
Part 8 — The list-style-image Property in Depth
When to Use a Background Image Instead of list-style-image
As mentioned earlier, list-style-image is limited. You cannot easily control the size, position, or spacing of the image. A more flexible technique is:
- Set
list-style-type: noneto remove the default bullet - Add
padding-leftto the<li>to make space for the image - Use
background-image+background-repeat: no-repeat+background-positionon the<li>
ul.custom-img-list {
list-style-type: none;
padding: 0;
margin: 0;
}
ul.custom-img-list li {
padding-left: 30px; /* space for the image */
background-image: url("arrow.png"); /* the image */
background-repeat: no-repeat; /* show it only once, not tiled */
background-position: left center; /* align image to left, centred vertically */
background-size: 18px 18px; /* control the size precisely */
margin-bottom: 8px;
line-height: 1.6;
}
Line by line:
padding-left: 30px;— pushes the text 30px away from the left, creating a visible gap where the image sitsbackground-image: url("arrow.png");— sets the image file as the backgroundbackground-repeat: no-repeat;— prevents the image from tiling/repeating across the elementbackground-position: left center;— places the image on the left side, vertically centred within the item’s line heightbackground-size: 18px 18px;— explicitly controls how large the image renders
Expected output: Each list item has a small icon on the left, with text cleanly pushed to the right of the icon.
This technique is used widely in professional web development for icon-prefixed feature lists.
Part 9 — Code Challenge Walkthroughs
The W3Schools CSS list code challenges test your ability to apply all the properties above. Here are the core challenge types with full solutions:
Challenge Type 1: Change the List Marker Type
Task: Give the unordered list a square marker style.
<ul>
<li>Item A</li>
<li>Item B</li>
</ul>
Solution:
ul {
list-style-type: square;
}
Challenge Type 2: Remove All Bullets
Task: Remove the bullet markers from the list.
Solution:
ul {
list-style-type: none;
}
Challenge Type 3: Set Position to Inside
Task: Place the list marker inside the list item’s content box.
Solution:
ul {
list-style-position: inside;
}
Challenge Type 4: Use the Shorthand
Task: Using the list-style shorthand, set the type to circle and position to inside.
Solution:
ul {
list-style: circle inside;
}
Challenge Type 5: Use a Custom Image Bullet
Task: Use the image "sqpurple.gif" as the list bullet.
Solution:
ul {
list-style-image: url("sqpurple.gif");
}
Challenge Type 6: Style a Specific <li> with Colour
Task: Give each list item a tomato background and white text, with 8px 16px padding.
Solution:
li {
background-color: tomato;
color: white;
padding: 8px 16px;
}
Challenge Type 7: Reset Default Padding and Margin
Task: Remove the default browser indentation from the list.
Solution:
ul {
padding: 0;
margin: 0;
}
Guided Practice Exercises
Exercise 1 — Feature Comparison List
Objective: Style an unordered list to look like a clean feature list, as seen on SaaS product websites.
Scenario: You are building a pricing page for a cloud storage app. Each plan has a list of features. Style the free plan’s feature list with square bullets, clean spacing, and a coloured background per item.
Starter HTML:
<!DOCTYPE html>
<html>
<head>
<style>
/* Write your CSS here */
</style>
</head>
<body>
<h2>Free Plan — Features</h2>
<ul class="feature-list">
<li>5 GB Storage</li>
<li>1 User Account</li>
<li>Basic File Sharing</li>
<li>Email Support</li>
</ul>
</body>
</html>
Target styling:
- No bullet markers
- Each item has a light blue background (
#e3f2fd) with a left blue border accent (4px solid #1565c0) - Padding of
10px 16pxper item - A small margin between items (
4px) - Dark navy text (
#1a237e) border-radius: 4pxfor softness
Solution:
ul.feature-list {
list-style-type: none;
padding: 0;
margin: 0;
width: 280px;
}
ul.feature-list li {
background-color: #e3f2fd;
border-left: 4px solid #1565c0;
color: #1a237e;
padding: 10px 16px;
margin-bottom: 4px;
border-radius: 4px;
font-size: 15px;
}
Expected output: A clean card-like feature list with blue left-accent borders. Each item is clearly separated.
Self-check questions:
- Does each item have a left blue border?
- Is there no default bullet showing?
- Is there a small gap between each item?
Optional What-If: What happens if you add border-radius: 0; to the items? How does the look change?
Exercise 2 — Ordered Step-by-Step Guide
Objective: Style an ordered list to look like a step-by-step guide card.
Scenario: You are building a “Getting Started” section for a developer tool. Style the numbered steps to look like milestone cards.
Starter HTML:
<ol class="steps-list">
<li>Create a free account</li>
<li>Install the command line tool</li>
<li>Run your first project</li>
<li>Deploy to production</li>
</ol>
Target styling:
- Use
upper-romannumerals - Position markers
inside - Background colour
#fff3e0(light orange) - Orange left border (
4px solid #e65100) - Padding
12px 18px, margin bottom6px - Font size
16px, colour#bf360c(dark orange) - Border radius
4px
Solution:
ol.steps-list {
list-style-type: upper-roman;
list-style-position: inside;
padding: 0;
margin: 0;
width: 320px;
}
ol.steps-list li {
background-color: #fff3e0;
border-left: 4px solid #e65100;
color: #bf360c;
padding: 12px 18px;
margin-bottom: 6px;
border-radius: 4px;
font-size: 16px;
}
Expected output: Four orange-tinted milestone cards with Roman numerals inside each one.
Exercise 3 — Horizontal Navigation Menu from a List
Objective: Transform an unordered list into a horizontal navigation bar.
Scenario: You are building a simple website header. Convert the list into a navigation bar with a dark background and white hover effects.
Starter HTML:
<ul class="topnav">
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Pricing</a></li>
<li><a href="#">Contact</a></li>
</ul>
Solution:
ul.topnav {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #2c3e50;
overflow: hidden;
}
ul.topnav li {
display: inline-block;
}
ul.topnav li a {
display: block;
color: #ecf0f1;
text-decoration: none;
padding: 14px 22px;
font-size: 15px;
font-family: Arial, sans-serif;
transition: background-color 0.2s ease;
}
ul.topnav li a:hover {
background-color: #1abc9c;
color: white;
}
Expected output: A dark slate-blue horizontal bar with light grey text links. On hover, each link gains a teal background.
Mini Project — Styled Recipe Page with Lists
Project Overview
You will build a complete recipe page that uses both unordered and ordered lists — styled as polished, modern UI components. The page will include: a list of ingredients (unordered) and a list of preparation steps (ordered).
Stage 1 — HTML Structure
<!DOCTYPE html>
<html>
<head>
<title>Classic Pasta Recipe</title>
<style>
/* CSS added in later stages */
</style>
</head>
<body>
<div class="recipe-card">
<h1>Classic Pasta al Pomodoro</h1>
<p class="subtitle">A simple, timeless Italian pasta dish ready in 30 minutes.</p>
<h2>🛒 Ingredients</h2>
<ul class="ingredients">
<li>400g spaghetti</li>
<li>800g canned tomatoes</li>
<li>3 cloves of garlic</li>
<li>Fresh basil leaves</li>
<li>4 tbsp olive oil</li>
<li>Salt and pepper to taste</li>
<li>Grated Parmesan cheese</li>
</ul>
<h2>👨🍳 Instructions</h2>
<ol class="instructions">
<li>Bring a large pot of salted water to the boil.</li>
<li>Finely slice the garlic cloves.</li>
<li>Heat olive oil in a pan and gently fry the garlic until golden.</li>
<li>Add canned tomatoes and simmer for 15 minutes.</li>
<li>Cook spaghetti according to package instructions.</li>
<li>Drain the pasta and toss with the tomato sauce.</li>
<li>Garnish with fresh basil and grated Parmesan.</li>
</ol>
</div>
</body>
</html>
Milestone output: Plain HTML with default browser list styling.
Stage 2 — Style the Page Container
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Georgia, serif;
background-color: #fdf6ec;
color: #333;
padding: 40px 20px;
}
.recipe-card {
max-width: 680px;
margin: 0 auto;
background-color: white;
border-radius: 12px;
padding: 40px 50px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
h1 {
font-size: 32px;
color: #c0392b;
margin-bottom: 8px;
}
.subtitle {
font-size: 15px;
color: #888;
margin-bottom: 32px;
font-style: italic;
}
h2 {
font-size: 20px;
color: #444;
margin-bottom: 16px;
margin-top: 32px;
border-bottom: 2px solid #f0e0d0;
padding-bottom: 6px;
}
Milestone output: A white card centred on a warm cream background. Title in warm red, section headers with bottom borders.
Stage 3 — Style the Ingredients List
ul.ingredients {
list-style-type: none;
padding: 0;
margin: 0;
}
ul.ingredients li {
padding: 9px 12px 9px 36px;
margin-bottom: 6px;
background-color: #fff8f0;
border-left: 3px solid #e67e22;
border-radius: 4px;
font-size: 15px;
position: relative;
}
ul.ingredients li::before {
content: "✓"; /* adds a tick before each item */
position: absolute;
left: 10px;
color: #e67e22;
font-weight: bold;
}
New concept — ::before pseudo-element:
li::before inserts content visually before each list item without adding it to the HTML. content: "✓" inserts a tick character. position: absolute with left: 10px places it precisely in the padding area we created with padding-left: 36px.
Milestone output: Each ingredient appears as a warm orange-bordered card with a tick mark on the left.
Stage 4 — Style the Instructions List
ol.instructions {
list-style-type: none;
padding: 0;
margin: 0;
counter-reset: step-counter; /* initialise a custom counter */
}
ol.instructions li {
padding: 12px 16px 12px 54px;
margin-bottom: 8px;
background-color: #fdf2f2;
border-left: 3px solid #c0392b;
border-radius: 4px;
font-size: 15px;
position: relative;
counter-increment: step-counter; /* increment for each li */
line-height: 1.6;
}
ol.instructions li::before {
content: counter(step-counter); /* show the counter number */
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
background-color: #c0392b;
color: white;
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
font-family: Arial, sans-serif;
}
New concepts here:
counter-reset: step-counter;— creates a CSS counter namedstep-counterand sets it to 0 on the<ol>. Think of it like a variable that starts at zero.counter-increment: step-counter;— on each<li>, the counter goes up by 1. First<li>→ 1, second → 2, etc.content: counter(step-counter);— in the::beforepseudo-element, this outputs the current number as text.- The round red circle with the number is built entirely in CSS using
border-radius: 50%,width/height, anddisplay: flexfor centering.
Milestone output: Each instruction step has a red circle with a step number on the left, like a progress timeline.
Stage 5 — Complete Final Page
Here is the complete, fully combined recipe page:
<!DOCTYPE html>
<html>
<head>
<title>Classic Pasta Recipe</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Georgia, serif;
background-color: #fdf6ec;
color: #333;
padding: 40px 20px;
}
.recipe-card {
max-width: 680px;
margin: 0 auto;
background-color: white;
border-radius: 12px;
padding: 40px 50px;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
h1 { font-size: 32px; color: #c0392b; margin-bottom: 8px; }
.subtitle { font-size: 15px; color: #888; margin-bottom: 32px; font-style: italic; }
h2 { font-size: 20px; color: #444; margin-bottom: 16px; margin-top: 32px;
border-bottom: 2px solid #f0e0d0; padding-bottom: 6px; }
/* ---- Ingredients ---- */
ul.ingredients { list-style-type: none; padding: 0; margin: 0; }
ul.ingredients li {
padding: 9px 12px 9px 36px;
margin-bottom: 6px;
background-color: #fff8f0;
border-left: 3px solid #e67e22;
border-radius: 4px;
font-size: 15px;
position: relative;
}
ul.ingredients li::before {
content: "✓";
position: absolute;
left: 10px;
color: #e67e22;
font-weight: bold;
}
/* ---- Instructions ---- */
ol.instructions {
list-style-type: none;
padding: 0;
margin: 0;
counter-reset: step-counter;
}
ol.instructions li {
padding: 12px 16px 12px 54px;
margin-bottom: 8px;
background-color: #fdf2f2;
border-left: 3px solid #c0392b;
border-radius: 4px;
font-size: 15px;
position: relative;
counter-increment: step-counter;
line-height: 1.6;
}
ol.instructions li::before {
content: counter(step-counter);
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
background-color: #c0392b;
color: white;
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<div class="recipe-card">
<h1>Classic Pasta al Pomodoro</h1>
<p class="subtitle">A simple, timeless Italian pasta dish ready in 30 minutes.</p>
<h2>🛒 Ingredients</h2>
<ul class="ingredients">
<li>400g spaghetti</li>
<li>800g canned tomatoes</li>
<li>3 cloves of garlic</li>
<li>Fresh basil leaves</li>
<li>4 tbsp olive oil</li>
<li>Salt and pepper to taste</li>
<li>Grated Parmesan cheese</li>
</ul>
<h2>👨🍳 Instructions</h2>
<ol class="instructions">
<li>Bring a large pot of salted water to the boil.</li>
<li>Finely slice the garlic cloves.</li>
<li>Heat olive oil in a pan and gently fry the garlic until golden.</li>
<li>Add canned tomatoes and simmer for 15 minutes.</li>
<li>Cook spaghetti according to package instructions.</li>
<li>Drain the pasta and toss with the tomato sauce.</li>
<li>Garnish with fresh basil and grated Parmesan.</li>
</ol>
</div>
</body>
</html>
Final output: A beautiful, professional recipe card with:
- Orange tick-marked ingredients in warm-toned bordered cards
- Red-numbered circular step indicators for instructions
- Clean white card on a cream background with soft shadow
Reflection Questions:
- What would happen if you removed
counter-resetfromol.instructions? Try it and observe. - How would you add a hover effect to each instruction step (e.g., slightly darker background)?
- What would change if you used
list-style-position: insideon the instructions before adding the custom counter?
Optional Extension: Add a third section — “Nutrition Info” — as a <dl> (definition list) with <dt> (term) and <dd> (definition) elements, styled with a two-column layout using CSS.
Common Beginner Mistakes
Mistake 1: Forgetting to Remove Default padding-left on <ul>
Wrong:
ul {
list-style-type: none;
/* forgot padding: 0 */
}
Problem: The list still has the browser’s default padding-left: 40px, so items appear indented even though there are no bullets. This catches many beginners off guard.
Correct:
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
Always reset both padding and margin when removing default list styles.
Mistake 2: Applying display: inline Instead of display: inline-block for Navigations
Wrong:
li {
display: inline;
padding: 12px 20px; /* vertical padding will not work correctly */
}
Problem: display: inline does not honour vertical padding in the same way as inline-block. The items will flow horizontally, but the padding will not expand the clickable area vertically.
Correct:
li {
display: inline-block;
padding: 12px 20px; /* now works perfectly */
}
Mistake 3: Not Adding display: block to the <a> Inside <li>
Wrong:
li a {
color: white;
padding: 12px 20px;
/* missing display: block */
}
Problem: The <a> is still an inline element. The padding will not create a full-height clickable area. Only the text itself is clickable, not the surrounding padded space.
Correct:
li a {
display: block; /* makes the full padded area clickable */
color: white;
padding: 12px 20px;
}
Mistake 4: Using list-style-image and Wondering Why the Image Looks Odd
Wrong approach (hard to control):
ul {
list-style-image: url("icon.png");
}
Problem: You cannot control the image size or position precisely.
Better approach:
ul {
list-style-type: none;
padding: 0;
}
li {
padding-left: 28px;
background-image: url("icon.png");
background-repeat: no-repeat;
background-position: left center;
background-size: 18px 18px;
}
This gives you complete control over image size and alignment.
Mistake 5: Applying list-style Shorthand in the Wrong Order
Wrong:
ul {
list-style: inside square; /* position before type */
}
Problem: The order matters. The shorthand is type position image. While most browsers are forgiving, writing them in an unusual order can cause confusion or unexpected results.
Correct:
ul {
list-style: square inside; /* type then position */
}
Mistake 6: Forgetting overflow: hidden on the <ul> Navbar
Wrong:
ul.navbar {
background-color: #333;
/* missing overflow: hidden */
}
ul.navbar li { display: inline-block; }
Problem: If the list items have a background colour, the parent <ul> may not visually contain them (the height collapses). Adding overflow: hidden forces the <ul> to stretch around its children.
Correct:
ul.navbar {
background-color: #333;
overflow: hidden; /* prevents height collapse */
}
Reflection Questions
-
What is the difference between
list-style-type: noneandlist-style: none? Which one removes more? -
When would you use
list-style-position: insideversusoutside? Give a real-world example of each. -
What is the most commonly used
list-style-typevalue for navigation menus? Why? -
If you want each list item to have a coloured background, do you apply the
background-colorto the<ul>or to each<li>? Why? -
Why do you need
display: blockon an<a>tag inside a navigation<li>? What does it change? -
What two CSS resets are almost always applied together when removing default list styling? Why are both needed?
-
Why is
background-imageon<li>generally better thanlist-style-imagefor custom bullet icons? -
What does
counter-resetdo, and why must it be placed on the<ol>rather than on the<li>?
Completion Checklist
Before moving on, confirm you can do all of the following:
- I can explain the difference between
<ul>and<ol>and when to use each - I can set
list-style-typetodisc,circle,square, andnone - I can set
list-style-typetodecimal,lower-roman,upper-roman,lower-alpha, andupper-alpha - I understand what
list-style-position: insideandoutsidedo and how wrapped text behaves differently - I can use the
list-styleshorthand to set type and position in one line - I can remove default browser padding and margin from a list
- I can style
<li>elements with background colour, padding, border, and border-radius - I can use
border-bottomandli:last-childto create a clean separator-style list - I can make a horizontal navigation bar from a
<ul>usingdisplay: inline-block - I understand why
display: blockon<a>inside<li>is important for click area - I can use
background-imageon<li>for a custom bullet icon with precise control - I can use CSS counters (
counter-resetandcounter-increment) with::beforefor custom numbering - I know the six most common beginner mistakes when styling lists and how to avoid them
Lesson Summary
In this lesson, you went from the raw HTML basics of <ul> and <ol> all the way to building professional, design-quality list components with CSS.
You learned that list-style-type controls the bullet or numbering marker, supporting types from simple shapes (disc, circle, square) to alphabetic and Roman numeral sequences. You learned that list-style-position controls whether the marker sits inside or outside the text content box, affecting how long lines of text wrap. You learned that list-style-image can replace a bullet with an image, and that the more powerful technique is using background-image on <li> for precise control. The shorthand list-style lets you set all three in a single clean line.
Beyond the dedicated list properties, you saw how removing default padding and margin, setting display: inline-block, and applying background-color, padding, and border directly to <li> elements transforms lists into the building blocks of navigation bars, feature panels, and step-by-step guides.
The mini-project brought everything together into a real-world recipe card — using custom CSS counters with ::before pseudo-elements to create beautifully styled step indicators that would feel at home on any professional website.
In the next lesson, you will explore CSS Tables — how to apply borders, spacing, alignment, and colour to HTML tables to turn raw data grids into polished, readable information displays.
Sources: W3Schools CSS Styling Lists Tutorial (https://www.w3schools.com/css/css_list.asp), CSS List Code Challenges (https://www.w3schools.com/css/css_challenges_list.asp)