HTML Tutorial
HTML Project: Restaurant Menu
A cafe menu grouped by section: starters, mains, and drinks, with prices in a readable table.
What you will build
A cafe menu for North Pine Kitchen. The page names the cafe, then three sections: starters, mains, and drinks. Each section is a table of item names and prices, not a decorative layout grid.
Prices are data. That is why they sit in <table>. Headings name the groups. A caption on each table tells a screen reader what the numbers mean.
Use Try it in HTML under an example to open /html/try. That is the HTML preview, not a Python window.
Tags you will use
| Tag | Job on this page |
|---|---|
header | Cafe name and a one-line promise |
section | One group: starters, mains, or drinks |
h1 / h2 | Page title, then each group title |
table, caption, th, td | Item and price rows |
thead and tbody | Header row versus data rows |
Do not fake columns with spaces in a paragraph. A table keeps the price aligned when the dish name is long.
Build in slices
First write the banner. A cafe needs a name people can say out loud.
Example
<header>
<h1>North Pine Kitchen</h1>
<p>Daytime plates, poured drinks, and a quiet window table.</p>
</header>Then add one section as a table. Caption first. Header cells for Dish and Price. Two data rows are enough to test alignment.
Example
<section>
<h2>Starters</h2>
<table>
<caption>Starters and prices in euros</caption>
<thead>
<tr>
<th scope="col">Dish</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pickled beet and rye</td>
<td>6.50</td>
</tr>
<tr>
<td>Leek soup</td>
<td>5.00</td>
</tr>
</tbody>
</table>
</section>Preview the slice at /html/try before you add mains and drinks. If the table looks like a blob, your CSS is missing, not the HTML.
Complete document
Three sections, three tables, one stylesheet. Borders stay light so the type does the work. Prices sit on the right of each row.
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>North Pine Kitchen menu</title>
<style>
body {
margin: 0;
background: #f4efe6;
color: #2a2118;
font-family: Georgia, serif;
}
header, main {
max-width: 36rem;
margin: 0 auto;
padding: 1.5rem 1.2rem 0;
}
header p { color: #5a4c3c; }
h1 { margin-bottom: 0.3rem; }
h2 {
margin: 1.6rem 0 0.5rem;
font-size: 1.15rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 0.4rem;
}
caption {
caption-side: bottom;
text-align: left;
font-size: 0.8rem;
color: #6b5c4b;
padding-top: 0.35rem;
}
th, td {
border-bottom: 1px solid #d7cbb8;
padding: 0.45rem 0;
}
th { text-align: left; }
td:last-child, th:last-child {
text-align: right;
font-variant-numeric: tabular-nums;
}
footer {
max-width: 36rem;
margin: 1.5rem auto 2rem;
padding: 0 1.2rem;
font-size: 0.9rem;
color: #5a4c3c;
}
</style>
</head>
<body>
<header>
<h1>North Pine Kitchen</h1>
<p>Daytime plates, poured drinks, and a quiet window table.</p>
</header>
<main>
<section>
<h2>Starters</h2>
<table>
<caption>Starters and prices in euros</caption>
<thead>
<tr><th scope="col">Dish</th><th scope="col">Price</th></tr>
</thead>
<tbody>
<tr><td>Pickled beet and rye</td><td>6.50</td></tr>
<tr><td>Leek soup</td><td>5.00</td></tr>
<tr><td>Warm olives</td><td>4.00</td></tr>
</tbody>
</table>
</section>
<section>
<h2>Mains</h2>
<table>
<caption>Mains and prices in euros</caption>
<thead>
<tr><th scope="col">Dish</th><th scope="col">Price</th></tr>
</thead>
<tbody>
<tr><td>Mushroom barley bowl</td><td>13.00</td></tr>
<tr><td>Trout on greens</td><td>16.50</td></tr>
<tr><td>Herb omelette</td><td>11.00</td></tr>
</tbody>
</table>
</section>
<section>
<h2>Drinks</h2>
<table>
<caption>Drinks and prices in euros</caption>
<thead>
<tr><th scope="col">Drink</th><th scope="col">Price</th></tr>
</thead>
<tbody>
<tr><td>Filter coffee</td><td>2.80</td></tr>
<tr><td>Pot of mint tea</td><td>3.20</td></tr>
<tr><td>Sparkling water</td><td>2.00</td></tr>
</tbody>
</table>
</section>
</main>
<footer>
<p>Kitchen closes at 15:00. Ask the counter about allergens.</p>
</footer>
</body>
</html>Evening board, same structure
A second complete menu proves the pattern. New cafe, new dishes, same three sections and tables.
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lamp Room evening board</title>
<style>
body {
margin: 0;
background: #1c1917;
color: #f5f0e8;
font-family: "Segoe UI", sans-serif;
}
header, main, footer { max-width: 34rem; margin: 0 auto; padding: 1.2rem; }
table { width: 100%; border-collapse: collapse; }
th, td { border-bottom: 1px solid #44403c; padding: 0.4rem 0; }
th { text-align: left; color: #d6d3d1; }
td:last-child, th:last-child { text-align: right; }
caption { caption-side: bottom; text-align: left; color: #a8a29e; font-size: 0.8rem; }
h2 { color: #fbbf24; font-size: 0.95rem; letter-spacing: 0.12em; text-transform: uppercase; }
</style>
</head>
<body>
<header>
<h1>Lamp Room</h1>
<p>Evening board. Small plates first.</p>
</header>
<main>
<section>
<h2>Starters</h2>
<table>
<caption>Starter prices in euros</caption>
<thead><tr><th scope="col">Dish</th><th scope="col">Price</th></tr></thead>
<tbody>
<tr><td>Bread and cultured butter</td><td>4.50</td></tr>
<tr><td>Cured trout</td><td>9.00</td></tr>
</tbody>
</table>
</section>
<section>
<h2>Mains</h2>
<table>
<caption>Main prices in euros</caption>
<thead><tr><th scope="col">Dish</th><th scope="col">Price</th></tr></thead>
<tbody>
<tr><td>Lamb shoulder, beans</td><td>22.00</td></tr>
<tr><td>Roast squash, hazelnut</td><td>16.00</td></tr>
</tbody>
</table>
</section>
<section>
<h2>Drinks</h2>
<table>
<caption>Drink prices in euros</caption>
<thead><tr><th scope="col">Drink</th><th scope="col">Price</th></tr></thead>
<tbody>
<tr><td>House red, glass</td><td>6.00</td></tr>
<tr><td>Still water</td><td>0.00</td></tr>
</tbody>
</table>
</section>
</main>
<footer><p>Last seating 21:00.</p></footer>
</body>
</html>Common mistakes
- Using one giant table for the whole menu. Split by section so headings stay meaningful.
- Skipping
thand putting Dish and Price in atd. Headers are not data. - Leaving out
scope="col". It costs nothing and helps the table make sense aloud. - Hiding prices in CSS generated content. If CSS fails, the price must still be in the cell.
- Laying out the page with a table of empty cells. Tables are for the dishes, not the chrome.
Practice tasks
- Add a fourth starter to North Pine Kitchen. Keep Dish and Price in separate cells.
- Rename the cafe and rewrite every dish. Keep starters, mains, and drinks as three sections.
- Add a
tfootnote under drinks that says service is included. Preview it at /html/try.