HTML Document Structure and Content
Before you start
You need: nothing. No web experience assumed.
You need installed: VS Code and a browser. This track uses VS Code rather than Visual Studio — it is what frontend work is done in, and the Live Server extension gives you instant reloads.
Time: about 45 minutes, plus the practice.
New to all of this? Start here explains what an application is made of and introduces the school system every example uses. Any word you do not recognise is in the glossary.
Learning objective
Write a valid HTML document from memory and choose the correct element for each piece of content.
Topics
- The document skeleton
<head>and the metadata that matters- Headings and document outline
- Text-level elements
- Links and paths
- Images,
alttext and responsive sizing - Lists
- Validating your HTML
The skeleton
<!DOCTYPE html>
<html lang="en-IN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student Records — NexCoding Academy</title>
<meta name="description" content="Student records for NexCoding Academy.">
<link rel="stylesheet" href="/css/site.css">
</head>
<body>
<h1>Student Records</h1>
<p>Manage admissions, classes and contact details.</p>
<script src="/js/site.js" defer></script>
</body>
</html>
Four lines in that head are not optional.
<!DOCTYPE html> puts the browser in standards mode. Omit it and you get quirks mode, where the box model behaves as it did in 1998 and your CSS produces different results in different browsers.
lang="en-IN" tells screen readers which language to pronounce, and browsers which dictionary and date conventions to use. It costs nothing.
<meta charset="UTF-8"> must appear within the first 1024 bytes. Without it, non-Latin characters render as ä or ?. For a school system holding names in Telugu, Hindi or Tamil, this is the difference between working and not.
The viewport meta tag tells a mobile browser to use the device width. Omit it and the phone renders the page at 980px and zooms out, so your responsive CSS never activates. Every media query you write is dead without this line.
Script placement
<script src="/js/site.js"></script> <!-- blocks parsing -->
<script src="/js/site.js" defer></script> <!-- downloads in parallel, runs after parsing -->
<script src="/js/site.js" async></script> <!-- runs as soon as it downloads, order not guaranteed -->
Use defer for anything touching the DOM. The script downloads while the page parses and runs once the document is ready — so document.getElementById finds elements, and the page is not blocked while downloading.
async suits independent scripts such as analytics. Its execution order is not guaranteed, so never use it for scripts that depend on each other.
Headings
<h1>Student Records</h1>
<h2>Class 10 — Section A</h2>
<h3>Contact details</h3>
<h3>Fee status</h3>
<h2>Class 10 — Section B</h2>
Headings form the document's outline. Screen-reader users navigate by jumping between them, and search engines use them to understand structure.
Two rules:
- One
<h1>per page, naming what the page is. - Never skip levels.
<h1>to<h3>leaves a gap that a screen reader reports as a missing section.
Headings are structure, not size. Choosing <h4> because it looks right is the most common HTML mistake there is — set the size in CSS and pick the heading by its place in the outline.
Text-level elements
<p>Ravi Kumar is in class 10, section A.</p>
<strong>Important:</strong> <!-- strong importance -->
<em>emphasised</em> <!-- stress emphasis -->
<b>bold</b> <!-- stylistically offset, no importance -->
<i>italic</i> <!-- alternate voice, e.g. a term -->
<code>SELECT * FROM Student</code>
<pre>preformatted, whitespace preserved</pre>
<abbr title="National Council of Educational Research and Training">NCERT</abbr>
<time datetime="2024-06-15">15 June 2024</time>
<small>Fees are non-refundable.</small>
<mark>highlighted</mark>
<br> <!-- line break within a block -->
<hr> <!-- thematic break -->
<strong> and <em> carry meaning; a screen reader changes intonation for them. <b> and <i> are purely visual. When the text genuinely matters, use <strong>.
<time datetime="2024-06-15"> gives machines an unambiguous date while showing humans whatever format you like. Useful for exam dates and due dates.
Never use <br> for spacing between paragraphs. Two paragraphs are two <p> elements; the gap belongs to CSS margin.
Links
<a href="/students/list.html">All students</a>
<a href="students/list.html">Relative to this page</a>
<a href="../index.html">Up one directory</a>
<a href="#fee-summary">Jump to a section on this page</a>
<a href="https://nexcoding.in">Another site</a>
<a href="mailto:info@nexcoding.in">Email us</a>
<a href="tel:+919951510727">Call us</a>
<a href="/reports/attendance.pdf" download>Download attendance report</a>
<a href="https://nexcoding.in" target="_blank" rel="noopener noreferrer">Opens in a new tab</a>
| Path | Resolves to |
|---|---|
/students/list.html | From the site root |
students/list.html | Relative to the current page |
../index.html | One directory up |
#section-id | An element on this page |
rel="noopener noreferrer" is required with target="_blank". Without noopener, the opened page can manipulate your page through window.opener — a real phishing vector. Modern browsers imply it, but older ones do not, and being explicit costs nothing.
Link text
<!-- Useless: a screen reader lists "click here, click here, click here" -->
<a href="/students/list.html">Click here</a> to see students.
<!-- Useful -->
<a href="/students/list.html">View all students</a>
Screen-reader users often navigate by pulling up a list of every link on the page, stripped of surrounding text. Link text must make sense alone.
Images
<img src="/img/campus.jpg"
alt="NexCoding Academy main building"
width="800" height="450"
loading="lazy">
alt is required on every image, and its content depends on the image's purpose:
<!-- Meaningful image: describe what it conveys -->
<img src="/img/ravi-kumar.jpg" alt="Ravi Kumar, class 10 section A">
<!-- Decorative image: empty alt, so screen readers skip it -->
<img src="/img/divider.svg" alt="">
<!-- Never omit the attribute — a screen reader then reads the filename -->
<img src="/img/ravi-kumar.jpg">
An empty alt="" and a missing alt are different. Empty means "skip this"; missing means the screen reader falls back to announcing the file path.
width and height prevent layout shift. The browser reserves the space before the image loads, so content does not jump as images arrive. Give the intrinsic pixel dimensions and let CSS scale it:
img { max-width: 100%; height: auto; }
loading="lazy" defers images below the fold. Do not use it on an image visible on first paint — it delays the one thing the user is waiting for.
Responsive images
<img src="/img/campus-800.jpg"
srcset="/img/campus-400.jpg 400w,
/img/campus-800.jpg 800w,
/img/campus-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 800px"
alt="NexCoding Academy main building">
srcset lists the available widths; sizes tells the browser how wide the image will render. The browser then picks the smallest file that will look sharp — which matters a great deal on a mobile connection.
<figure>
<img src="/img/results-chart.png" alt="Class 10 pass rates, 2020 to 2024">
<figcaption>Class 10 pass rates have risen from 78% to 94% since 2020.</figcaption>
</figure>
Use <figure> when the image needs a visible caption. Note the alt and the caption say different things — the caption is for everyone, the alt describes the image to someone who cannot see it.
Lists
<!-- Unordered: order does not matter -->
<ul>
<li>Mathematics</li>
<li>Science</li>
<li>English</li>
</ul>
<!-- Ordered: sequence is meaningful -->
<ol>
<li>Submit the admission form</li>
<li>Pay the first instalment</li>
<li>Collect the identity card</li>
</ol>
<!-- Description: name/value pairs -->
<dl>
<dt>Roll number</dt>
<dd>NCA-2024-0012</dd>
<dt>Class</dt>
<dd>10 — Section A</dd>
<dt>Parent contact</dt>
<dd>Suresh Kumar, 9951510727</dd>
</dl>
<dl> is the right element for a details panel — a set of labels and values. It is widely forgotten, and a <table> or a pile of <div>s is used instead.
Lists nest inside <li>, never directly inside <ul>:
<ul>
<li>Class 10
<ul>
<li>Section A</li>
<li>Section B</li>
</ul>
</li>
</ul>
Navigation is a list of links:
<nav aria-label="Main">
<ul>
<li><a href="/students/">Students</a></li>
<li><a href="/teachers/">Teachers</a></li>
<li><a href="/fees/">Fees</a></li>
</ul>
</nav>
That structure lets a screen reader announce "list of 3 items" — useful orientation that a row of bare <a> elements does not provide.
Validation
Run every page through the W3C validator. Browsers silently repair broken HTML, and they repair it differently — which is why a layout can be correct in Chrome and wrong in Safari.
The errors worth fixing first:
- Unclosed elements
- Elements nested where they are not permitted (a
<div>inside a<p>) - Duplicate
idvalues - Missing
alt - Missing required attributes
Duplicate ids deserve special mention. document.getElementById returns only the first, and CSS #id styles only the first — so a form field silently stops working with no error anywhere.
Errors you will hit
| What you see | Cause | Fix |
|---|---|---|
| Page renders as plain text | Wrong file extension, or opened as a file path oddly | Save as .html; use Live Server |
| Changes do not appear | Browser cached the old file | Ctrl+Shift+R, or DevTools → Disable cache |
| Everything after a point is bold | An unclosed tag | Check the Elements panel for the real nesting |
| Special characters show as boxes | Missing <meta charset="utf-8"> | Add it in <head> |
| The page shows in DevTools but not the browser | You are looking at an element with display: none | Check the Styles pane |
View Source shows what the server sent; the Elements panel shows the live DOM. When they disagree, the difference is what your JavaScript did.
Common mistakes
- Missing
<!DOCTYPE html>, so the browser uses quirks mode - Missing the viewport meta tag, so responsive CSS never activates
- Missing
charset, so non-Latin names render as? - Choosing headings by size rather than by outline position
- Skipping heading levels
- More than one
<h1> <br>for spacing between paragraphs- Missing
alt, oralton a decorative image that should be empty - No
widthandheight, causing layout shift loading="lazy"on an above-the-fold image- "Click here" link text
target="_blank"withoutrel="noopener"- Duplicate
idvalues - Never validating
Practice
The course exercise is build a semantic form; this article covers the document it lives in.
- Write a complete student profile page from memory: doctype,
lang, charset, viewport, title, description, one<h1>. - Add a details panel using
<dl>for roll number, class, date of birth and parent contact. - Add a
<figure>with a student photo, correctalt, explicitwidthandheight, and a caption. Confirm thealtand caption say different things. - Add navigation as a
<nav>containing a<ul>of links. - Add an ordered list of admission steps and an unordered list of subjects.
- Run the page through the W3C validator and fix every error.
- Remove the viewport meta tag, open the page on a phone or in device emulation, and record what happens.
- Remove
charsetand put a Telugu or Hindi name in the page. Record what renders. - Give two elements the same
id, then try to select the second withgetElementById. Confirm you get the first. - Add
widthandheightto one image and omit them on another. Throttle the network in DevTools and watch which one shifts the layout.
Exercises 7 and 8 are the two that break a real site, and both are one missing line.
You can now
- Write a valid HTML document from memory
- Choose elements by meaning rather than appearance
- Say what
<head>is for and what belongs there - Nest elements correctly and find an unclosed tag
- Tell View Source from the Elements panel
Review questions
- What breaks without the viewport meta tag?
- What is the difference between a missing
altandalt=""? - Why should heading levels never be chosen for their size?
- Why do
widthandheighton an image matter even when CSS resizes it?
Next: Tables and forms