What HTML5 + CSS Are (and Aren't)

HTML (HyperText Markup Language) is a document description language, not a programming language. It has no variables, loops, or functions. It tells the browser what content exists and what role each piece plays — heading, paragraph, image, link, form field.

CSS (Cascading Style Sheets) is a rule-based styling language. It selects HTML elements and applies visual properties: colour, size, layout, animation. CSS is Turing-incomplete but surprisingly expressive.

JavaScript (not covered here) adds behaviour and interactivity. Together the three form the standard web stack. This guide covers the first two in full.

Mental model for programmers Think of HTML as the AST (abstract syntax tree) of a document — pure structure, no logic. CSS is a pattern-matching rule engine: selectors are patterns, declarations are transformations applied when a pattern matches.

How a browser processes HTML + CSS

  1. Parse HTML → build DOM (Document Object Model) — a tree of nodes.
  2. Parse CSS → build CSSOM (CSS Object Model) — a tree of rules.
  3. Combine into Render Tree → compute layout → paint pixels.
  4. Any resource referenced (image, font, stylesheet) triggers a separate HTTP request.

HTML5 Language Reference

1.1 — Document Structure

Every HTML document follows a fixed skeleton. The browser uses the doctype declaration to choose the parsing algorithm (standard mode vs. quirks mode).

<!-- The doctype MUST be the first line. No whitespace before it. -->
<!DOCTYPE html>

<html lang="en">          <!-- root element; lang= helps screen readers -->

  <head>                  <!-- metadata — nothing here renders visibly -->
    <meta charset="UTF-8">                <!-- character encoding -->
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0"> <!-- mobile -->
    <meta name="description" content="Page description for SEO">
    <title>Page Title — appears in tab</title>

    <!-- External CSS -->
    <link rel="stylesheet" href="style.css">

    <!-- Inline CSS (avoid; use for critical/above-fold styles) -->
    <style> body { margin: 0; } </style>
  </head>

  <body>                  <!-- everything rendered on screen lives here -->

    <!-- JavaScript — place at END of body so HTML parses first -->
    <script src="app.js" defer></script>
  </body>

</html>
Self-closing vs. void elements HTML5 does NOT use <br /> XML-style. Void elements (meta, link, br, hr, img, input) have no closing tag. Writing <br> is correct. Writing <br/> is tolerated but not required.

1.2 — Semantic Elements

Semantic elements carry meaning beyond visual layout. They help search engines, screen readers, and other tools understand document structure. Prefer them over generic <div> / <span> blocks.

ElementRoleNotes
<header>Page or section headerCan appear multiple times (once per section)
<nav>Navigation linksFor major navigation blocks only
<main>Primary contentOne per page; skip-to-content target
<article>Self-contained contentBlog post, news item, comment
<section>Thematic groupingShould have a heading
<aside>Tangentially related contentSidebar, pull-quote, ad
<footer>Page or section footerCopyright, links, contact
<figure>Self-contained mediaImage + caption pair
<figcaption>Caption for <figure>First or last child of <figure>
<time>Date/timedatetime="2025-01-15" for machines
<mark>Highlighted/relevant textSearch-result highlight
<details> / <summary>Disclosure widgetNative accordion — no JS!
<dialog>Modal/popupNative modal — JS to open/close
<address>Contact infoFor nearest <article> or <body>
<!-- Typical page skeleton using semantic elements -->
<body>
  <header>
    <nav>...</nav>
  </header>

  <main>
    <article>
      <h1>Post Title</h1>
      <time datetime="2025-06-01">June 1, 2025</time>
      <section>...</section>
    </article>
    <aside>...sidebar...</aside>
  </main>

  <footer>...</footer>
</body>

1.3 — Headings, Text & Inline Elements

<!-- Headings: h1–h6. Only ONE h1 per page (SEO). -->
<h1>Primary heading</h1>
<h2>Section heading</h2>
<h3>Sub-section</h3>   <!-- ... through h6 -->

<!-- Paragraph -->
<p>Paragraph text. Block-level — starts a new line.</p>

<!-- Inline text elements -->
<strong>Bold (semantic importance)</strong>
<em>Italic (semantic emphasis)</em>
<b>Bold (purely visual)</b>
<i>Italic (technical term, thought)</i>
<u>Underline</u>
<s>Strikethrough</s>
<sup>Superscript</sup>  <sub>Subscript</sub>
<code>inline code</code>
<pre>preformatted — preserves whitespace & newlines</pre>
<abbr title="HyperText Markup Language">HTML</abbr>
<span>generic inline container</span>
<br>   <!-- line break — avoid; use CSS margin -->
<hr>   <!-- thematic break / horizontal rule -->

<!-- Hyperlink -->
<a href="https://example.com"    <!-- absolute URL -->
   href="about.html"             <!-- relative path -->
   href="#section-id"           <!-- same-page anchor -->
   href="mailto:hi@example.com"  <!-- email -->
   target="_blank"              <!-- open in new tab -->
   rel="noopener noreferrer">   <!-- security for _blank -->
  Link text
</a>

1.4 — Lists & Tables

<!-- Unordered list -->
<ul>
  <li>Item</li>
  <li>Item</li>
</ul>

<!-- Ordered list -->
<ol start="3" reversed>         <!-- start= offset; reversed= count down -->
  <li value="10">Override number</li>
</ol>

<!-- Description list (key-value pairs) -->
<dl>
  <dt>Term</dt>   <dd>Definition</dd>
</dl>

<!-- Table -->
<table>
  <caption>Quarterly Results</caption>
  <thead>
    <tr>
      <th scope="col">Q1</th>
      <th scope="col">Q2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="2">spans 2 rows</td>
      <td colspan="2">spans 2 cols</td>
    </tr>
  </tbody>
  <tfoot>...</tfoot>
</table>

1.5 — Forms & Input Types

How forms work A <form> collects data. On submit, data is sent via HTTP GET (appended to URL as query string) or POST (in request body) to the action URL. Without JavaScript you need a server to process the submission.
<form
  action="/submit"      <!-- URL to send data to -->
  method="post"        <!-- "get" or "post" -->
  enctype="multipart/form-data"  <!-- required for file uploads -->
  novalidate           <!-- skip browser validation (for custom JS) -->
>

  <!-- label ALWAYS links to its input via for= / id= -->
  <label for="username">Username</label>
  <input
    type="text"
    id="username"
    name="username"       <!-- key in submitted data -->
    value="prefilled"
    placeholder="hint text"
    required              <!-- boolean attribute -->
    minlength="3" maxlength="20"
    pattern="[a-z]+"     <!-- regex validation -->
    autocomplete="username"
    autofocus
  >

  <!-- All HTML5 input types -->
  <input type="text">           <!-- plain text -->
  <input type="password">       <!-- masked -->
  <input type="email">          <!-- email validation + mobile keyboard -->
  <input type="url">            <!-- URL validation -->
  <input type="tel">            <!-- telephone; numeric keyboard on mobile -->
  <input type="number" min="0" max="100" step="5">
  <input type="range"  min="0" max="10" step="1">  <!-- slider -->
  <input type="date">           <!-- date picker -->
  <input type="time">
  <input type="datetime-local">
  <input type="month">
  <input type="week">
  <input type="color">          <!-- colour picker -->
  <input type="file" accept=".pdf,image/*" multiple>
  <input type="checkbox" checked>
  <input type="radio" name="group" value="a">  <!-- same name= groups radios -->
  <input type="hidden" value="csrf-token">
  <input type="search">
  <input type="submit" value="Send">
  <input type="button" value="Click">
  <input type="reset">           <!-- clears form -->
  <input type="image" src="btn.png">  <!-- image submit button -->

  <!-- Multi-line text -->
  <textarea rows="5" cols="40" name="msg">default</textarea>

  <!-- Dropdown -->
  <select name="colour" multiple size="4">
    <optgroup label="Warm">
      <option value="red" selected>Red</option>
    </optgroup>
  </select>

  <!-- Datalist (autocomplete suggestions) -->
  <input list="cities" name="city">
  <datalist id="cities">
    <option value="Atlanta">
  </datalist>

  <!-- Grouping fields -->
  <fieldset>
    <legend>Group label</legend>
    ...inputs...
  </fieldset>

  <!-- Styled button (preferred over input type=submit) -->
  <button type="submit">Submit</button>
  <button type="button">JS trigger</button>
  <button type="reset">Clear</button>
</form>
Live Demo — rendered form elements

1.6 — Images, Media & Embeds

<!-- Image -->
<img
  src="photo.jpg"
  alt="Descriptive text"    <!-- required; "" for decorative images -->
  width="800" height="600"   <!-- prevents layout shift -->
  loading="lazy"           <!-- native lazy loading -->
>

<!-- Responsive image -->
<picture>
  <source media="(min-width:800px)" srcset="large.jpg">
  <source media="(min-width:400px)" srcset="medium.jpg">
  <img src="small.jpg" alt="...">  <!-- fallback -->
</picture>

<!-- Video -->
<video controls autoplay muted loop poster="thumb.jpg" width="640">
  <source src="video.mp4"  type="video/mp4">
  <source src="video.webm" type="video/webm">
  <!-- fallback text if browser can't play video -->
</video>

<!-- Audio -->
<audio controls src="track.mp3"></audio>

<!-- Embed external page / map / YouTube -->
<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  width="560" height="315"
  allowfullscreen
  loading="lazy"
  title="Video description"   <!-- required for accessibility -->
></iframe>

<!-- SVG inline (vectors scale perfectly) -->
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="40" fill="#c84b2f"/>
</svg>

1.7 — Global & Data Attributes

These attributes are valid on any HTML element.

AttributePurpose
id="unique"Unique identifier per page — used by CSS #id, JS, and anchor links
class="a b c"Space-separated class list — primary CSS hook
style="color:red"Inline CSS — highest specificity; avoid in production
hiddenHides element (display:none); preserve semantics
tabindex="0"Makes element keyboard-focusable; -1 = programmatic only
title="tooltip"Tooltip on hover
lang="fr"Language of element's content
dir="rtl"Text direction: ltr / rtl / auto
contenteditableMakes element editable like a text field
draggableEnables drag-and-drop API
data-*Custom data attributes — store arbitrary data on elements
aria-*Accessibility attributes (aria-label, aria-hidden, aria-expanded…)
<!-- data-* attributes — store any value on any element -->
<button
  data-user-id="42"
  data-action="delete"
>Delete</button>

<!-- Read in JavaScript: -->
// btn.dataset.userId  → "42"
// btn.dataset.action  → "delete"

1.8 — HTML Entities & Special Characters

EntityCharacterUse when
&amp;&Ampersand in text / attributes
&lt; / &gt;< >Angle brackets in content
&quot;"Quote inside an attribute
&apos;'Apostrophe inside attribute
&nbsp;(non-breaking space)Prevent line break between words
&copy;©Copyright symbol
&mdash;Em dash
&hellip;Ellipsis

CSS Reference

2.1 — Syntax & Linking Methods

/* ── Basic rule anatomy ── */
selector {              /* matches elements in the DOM */
  property: value;      /* declaration — note the colon and semicolon */
  another-property: value;
}

/* ── Three ways to include CSS ── */

/* 1. External stylesheet (best practice) */
/* In <head>: <link rel="stylesheet" href="styles.css"> */

/* 2. <style> block in <head> (ok for single-page or critical CSS) */
/* <style> ... </style> */

/* 3. Inline (avoid — mixes content and presentation) */
/* <p style="color:red"> */

/* ── Comments ── */
/* CSS only has block comments — no // line comments */

2.2 — Selectors

SelectorMatchesExample
Type / tagAll elements of that typep { }
ClassElements with that class.card { }
IDElement with that id (one per page)#hero { }
UniversalEvery element* { }
AttributeHas attribute[disabled] { }
Attribute =Exact value[type="text"] { }
Attribute ^=Value starts with[href^="https"] { }
Attribute $=Value ends with[src$=".png"] { }
Attribute *=Value contains[class*="btn"] { }
DescendantB anywhere inside Anav a { }
Child >Direct children onlyul > li { }
Adjacent +Immediately following siblingh1 + p { }
General ~All following siblingsh2 ~ p { }
:hoverMouse overa:hover { }
:focusKeyboard / click focusedinput:focus { }
:activeBeing clickedbutton:active { }
:nth-child(n)nth child of parentli:nth-child(odd) { }
:first-child / :last-childFirst/last childp:first-child { }
:not(x)Doesn't match xli:not(.active) { }
:is(x,y)Matches x or y:is(h1,h2,h3) { }
::before / ::afterGenerated pseudo-elementp::before { content:"» "; }
::placeholderInput placeholder textinput::placeholder { }
::selectionUser-selected text::selection { background:yellow; }
,Group: apply to allh1, h2, h3 { }

2.3 — Specificity & the Cascade

When two rules conflict, the one with higher specificity wins. Specificity is a 3-digit weight: IDs · Classes / Attributes / Pseudo-classes · Elements / Pseudo-elements.

1
0
1
#nav a = 1 ID, 0 class, 1 tag
0
2
1
.nav .link a
0
0
3
ul li a
Cascade rules in order 1 · Importance (!important) — use sparingly, breaks cascade.
2 · Specificity — higher score wins.
3 · Source order — later rule wins on a tie.

2.4 — Custom Properties (CSS Variables)

/* Define on :root (global) or any selector (scoped) */
:root {
  --brand-color: #c84b2f;
  --spacing-md:  1.5rem;
  --font-main:   'Source Serif 4', Georgia, serif;
}

/* Use with var() */
h1 {
  color:       var(--brand-color);
  margin-top:  var(--spacing-md);
  font-family: var(--font-main);
}

/* var() accepts a fallback */
p { color: var(--text-color, #333); }

/* Scope to a component — overrides :root within .card */
.card { --brand-color: #1e6b6b; }

2.5 — The Box Model

Every element is a rectangular box with four areas (outside → in):

MARGIN — outside; pushes other elements away; transparent
BORDER — visible edge; has width, style, color
PADDING — inside; same background as content
CONTENT
width × height
div {
  /* Individual sides: top right bottom left (clockwise) */
  margin:  10px 20px 10px 20px;   /* 4 values */
  margin:  10px 20px;             /* top/bottom  left/right */
  margin:  10px;                  /* all sides */
  margin-top: 10px;              /* individual side */
  margin: 0 auto;               /* centre block horizontally */

  padding: 16px;                 /* same shorthand as margin */

  border:        2px solid #333;
  border-radius: 8px;            /* rounded corners */
  border-radius: 50%;           /* circle (on square element) */

  width:  300px;
  height: 200px;

  /* box-sizing: border-box — widths INCLUDE padding+border (use this!) */
  box-sizing: border-box;   /* most frameworks apply * { box-sizing: border-box } */

  overflow: hidden;          /* visible | hidden | scroll | auto */
}

2.6 — Display Types

ValueBehaviour
blockStarts on new line, takes full available width. <div>, <p>, headings
inlineFlows in text line; width/height ignored. <span>, <a>, <strong>
inline-blockFlows inline but respects width/height
flexBlock-level flex container → children become flex items
inline-flexSame as flex but container is inline
gridBlock-level grid container
inline-gridSame as grid but inline
noneRemoved from layout entirely (not just invisible)
contentsElement itself disappears; children remain

2.7 — Positioning

div {
  position: static;    /* default — in normal flow, top/left ignored */
  position: relative; /* offset from own normal position; still in flow */
  position: absolute; /* removed from flow; placed relative to nearest
                          positioned ancestor (not static) */
  position: fixed;    /* removed from flow; relative to viewport — stays on scroll */
  position: sticky;  /* relative until scrolled to threshold, then fixed */

  /* Offset properties (used with non-static) */
  top:    20px;
  right:  0;
  bottom: 0;
  left:   50%;

  /* Stack order for positioned elements */
  z-index: 10;  /* higher = on top; only works on positioned elements */
}

/* Centring pattern (absolute/fixed) */
.overlay {
  position: absolute;
  top:  50%; left: 50%;
  transform: translate(-50%, -50%); /* shift back by half own size */
}

2.8 — Flexbox

Flexbox is a one-dimensional layout system — it handles either a row or a column at a time. Perfect for navigation bars, card rows, centring, and components.

/* ── Container properties ── */
.container {
  display:         flex;          /* activate */

  flex-direction:  row;          /* row | row-reverse | column | column-reverse */
  flex-wrap:       wrap;         /* nowrap | wrap | wrap-reverse */
  flex-flow:       row wrap;     /* shorthand: direction + wrap */

  justify-content: flex-start;  /* MAIN axis: start|end|center|space-between
                                      |space-around|space-evenly */
  align-items:     stretch;     /* CROSS axis: stretch|flex-start|flex-end
                                      |center|baseline */
  align-content:   flex-start;  /* multi-line cross axis packing */
  gap:             1rem;         /* space between items (row-gap column-gap) */
}

/* ── Item properties ── */
.item {
  flex-grow:   1;     /* proportion of spare space to consume (0 = none) */
  flex-shrink: 1;     /* whether item can shrink (1 = yes) */
  flex-basis:  auto;  /* initial size before growing/shrinking */
  flex:        1 1 auto; /* shorthand: grow shrink basis */
  flex:        1;         /* means 1 1 0% */

  align-self:  center; /* override align-items for this item */
  order:       2;      /* visual order (default 0) */
}
Live Demo — Flexbox layouts
justify-content: flex-start (default)
A
B
C
justify-content: space-between
A
B
C
justify-content: center + align-items: center
A
B
C

2.9 — CSS Grid

Grid is two-dimensional — rows and columns simultaneously. Best for page layouts and complex component grids.

/* ── Container ── */
.grid {
  display: grid;

  /* Define columns */
  grid-template-columns: 200px 1fr 1fr;    /* fixed + 2 flexible */
  grid-template-columns: repeat(3, 1fr);   /* 3 equal columns */
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* responsive! */

  /* Define rows */
  grid-template-rows: auto 1fr auto;       /* header, content, footer */

  /* Named areas — visual layout map */
  grid-template-areas:
    "header header"
    "main   sidebar"
    "footer footer";

  gap: 1rem;         /* shorthand for row-gap + column-gap */
}

/* ── Assign items to areas ── */
.header  { grid-area: header; }
.main    { grid-area: main; }
.sidebar { grid-area: sidebar; }
.footer  { grid-area: footer; }

/* ── Or use line numbers ── */
.spanning {
  grid-column: 1 / 3;   /* start line / end line */
  grid-column: 1 / -1;  /* -1 = last line = full width */
  grid-column: span 2;  /* span 2 columns from current position */
  grid-row:    2 / 4;
}
Live Demo — CSS Grid named areas (2-col layout)
header (spans both cols)
main (2fr)
sidebar (1fr)
footer (spans both cols)

2.10 — Colours & Typography

/* ── Colour formats ── */
color: red;                       /* named (148 keywords) */
color: #c84b2f;                   /* hex RGB */
color: #c84b2f80;                 /* hex + alpha (last 2 digits) */
color: rgb(200, 75, 47);          /* rgb() */
color: rgba(200, 75, 47, 0.5);    /* rgba() with alpha 0–1 */
color: hsl(12, 63%, 48%);        /* hue (0-360) saturation% lightness% */
color: oklch(55% 0.15 30);       /* modern perceptual colour space */

/* ── Backgrounds ── */
background-color:   #faf6ef;
background-image:   url('bg.jpg');
background-size:    cover;              /* cover | contain | 100% auto */
background-position: center center;
background-repeat:  no-repeat;
background:         linear-gradient(135deg, #c84b2f, #1e6b6b);
background:         radial-gradient(circle at 30% 50%, #fff 0%, #c84b2f 100%);

/* Multiple backgrounds (comma-separated, first = top layer) */
background: url('texture.png') top left repeat,
             linear-gradient(to bottom, #fff, #eee);

/* ── Typography ── */
font-family:  'Playfair Display', Georgia, serif;   /* fallback chain */
font-size:    1rem;          /* rem = root em — scales with user prefs */
font-size:    clamp(1rem, 2.5vw, 1.5rem); /* fluid type */
font-weight:  400;           /* 100–900; 400=normal 700=bold */
font-style:   italic;        /* normal | italic | oblique */
line-height:  1.6;           /* unitless — relative to font-size */
letter-spacing: .05em;
text-align:   center;        /* left | right | center | justify */
text-transform: uppercase;  /* uppercase | lowercase | capitalize */
text-decoration: underline; /* none | underline | line-through */
text-shadow: 2px 2px 4px rgba(0,0,0,.3); /* x y blur colour */

/* Google Fonts — load in <head> before using */
/* <link href="https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap" rel="stylesheet"> */

2.11 — Transforms

.el {
  transform: translate(20px, -10px);  /* move — doesn't affect layout */
  transform: scale(1.2);             /* scale uniformly */
  transform: scale(1.5, 0.8);        /* x y independently */
  transform: rotate(45deg);         /* clockwise */
  transform: skew(10deg, 5deg);     /* shear */

  /* Chain multiple (applied right-to-left) */
  transform: rotate(30deg) scale(0.8) translate(50%, 0);

  /* Origin of transform */
  transform-origin: top left;   /* default: center center */
}

2.12 — Transitions & Animations

/* ── Transitions (A → B on state change) ── */
.btn {
  background:  #c84b2f;
  transform:   translateY(0);

  /* property  duration  easing          delay */
  transition: background .3s ease, transform .3s ease;
  /* transition: all .3s ease;   ← convenience, less performant */
}
.btn:hover {
  background: #9e3a22;
  transform:  translateY(-2px);
}

/* Easing keywords: linear | ease | ease-in | ease-out | ease-in-out */
/* cubic-bezier(x1, y1, x2, y2) for custom curves */

/* ── Keyframe animations (run continuously or on trigger) ── */
@keyframes pulse {
  0%    { transform: scale(1);    opacity: 1; }
  50%   { transform: scale(1.1);  opacity: .8; }
  100%  { transform: scale(1);    opacity: 1; }
}

/* Can also use from / to (shorthand for 0% / 100%) */
@keyframes slide-in {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}

.animated {
  animation-name:            pulse;
  animation-duration:        1.4s;
  animation-timing-function: ease-in-out;
  animation-delay:           0.2s;
  animation-iteration-count: infinite;    /* or a number */
  animation-direction:       alternate;   /* normal | reverse | alternate */
  animation-fill-mode:       both;        /* none | forwards | backwards | both */
  animation-play-state:      running;     /* running | paused */

  /* Shorthand: name duration easing delay count direction fill-mode */
  animation: pulse 1.4s ease-in-out 0s infinite alternate both;
}

/* Staggered entrance — add delay per item */
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: .1s; }
.item:nth-child(3) { animation-delay: .2s; }
Live Demo — CSS Animations
spin
rotate(360deg)
pulse
scale(1.15)
slide
translateX
Live Demo — CSS Transitions (hover over buttons)

2.13 — Shadows, Filters & Miscellaneous

/* Box shadow */
box-shadow: 0 2px 8px rgba(0,0,0,.15);   /* x y blur colour */
box-shadow: 0 0 0 3px #c84b2f;            /* outline ring */
box-shadow: inset 0 2px 4px rgba(0,0,0,.2); /* inner shadow */
/* multiple shadows: */
box-shadow: 0 2px 8px rgba(0,0,0,.1), 0 8px 24px rgba(0,0,0,.15);

/* Text shadow: x y blur colour */
text-shadow: 1px 1px 3px rgba(0,0,0,.3);

/* Opacity: 0 = invisible, 1 = fully visible */
opacity: 0.8;

/* CSS Filters */
filter: blur(4px);
filter: grayscale(100%);
filter: brightness(1.2) contrast(0.9);
filter: drop-shadow(2px 4px 6px rgba(0,0,0,.4));

/* Cursor */
cursor: pointer;   /* default|pointer|not-allowed|grab|crosshair|text|... */

/* Viewport units */
width:  100vw;    /* 100% of viewport width */
height: 100vh;   /* 100% of viewport height */
height: 100dvh;  /* dynamic vh — mobile address-bar aware */

2.14 — Media Queries & Responsive Design

CSS is mobile-first by convention: write base styles for small screens, then use min-width queries to layer in wider-screen styles.

mobile
< 640px
tablet
640px–1024px
desktop
> 1024px
/* ── Mobile-first approach ── */
/* Base styles (mobile) — no query needed */
.grid { display: grid; grid-template-columns: 1fr; }

/* Tablet and up */
@media (min-width: 640px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}

/* Desktop and up */
@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}

/* ── Other query types ── */
@media (max-width: 480px)             { /* phones only */ }
@media (orientation: landscape)       { /* landscape mode */ }
@media (prefers-color-scheme: dark)   { /* dark mode */ }
@media (prefers-reduced-motion: reduce) {
  /* disable animations for vestibular disorder users */
  * { animation-duration: 0.01ms !important; }
}
@media print                           { /* print styles */ }

/* Combine with 'and', 'or' (comma), 'not' */
@media (min-width: 600px) and (orientation: landscape) { }

Complete Beginner Website

A single index.html file — no server, no build tool, no framework. Open it directly in a browser. It demonstrates every major HTML5 + CSS concept covered in this guide.

What this project covers Semantic layout (header/nav/main/article/aside/footer) · CSS custom properties · Sticky navigation · Flexbox nav bar · CSS Grid 2-column layout · Hero section with gradient + keyframe animation · Responsive single-column breakpoint · Forms with multiple input types · CSS Grid form layout · Hover transitions · Details/summary accordion · Figure+figcaption · Description list · HTML entities · Google Fonts
How to use this preview The live site above is rendered directly from the annotated source below — inside an <iframe srcdoc="...">. Scroll down to read the fully annotated source, then copy it into your own index.html file.

Annotated Source Code

Copy everything below into a file called index.html and open it in your browser. Comments explain every section.

<!-- ════════════════════════════════════════════
     index.html — Hello World website
     Pure HTML5 + CSS. Open directly in a browser.
     ════════════════════════════════════════════ -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Hello World — My First Website</title>

  <!-- Google Fonts — load BEFORE using font-family -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=DM+Sans:wght@300;400;600&display=swap"
        rel="stylesheet">

  <style>
  /* ── CSS custom properties (design tokens) ──────────── */
  /* Change these two lines to retheme the entire site:   */
  :root {
    --accent:  #b5472a;  /* rust red  */
    --accent2: #2a6b6b;  /* teal      */
    --bg:      #f7f4ee;  /* warm cream background */
    --surface: #ffffff;
    --ink:     #1a1714;  /* near-black text */
    --mid:     #5a5248;  /* secondary text */
    --muted:   #9a9189;
    --rule:    #e0d8cc;  /* borders */
    --radius:  6px;
    --font-serif: 'Libre Baskerville', Georgia, serif;
    --font-sans:  'DM Sans', system-ui, sans-serif;
  }

  /* ── Reset ── */
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  html { scroll-behavior: smooth; font-size: 16px; }
  body {
    background: var(--bg);
    color:       var(--ink);
    font-family: var(--font-sans);
    font-weight: 300;
    line-height: 1.7;
  }

  /* ── Sticky navigation bar (Flexbox) ── */
  .site-nav {
    position: sticky;   /* sticks to top when scrolled to */
    top: 0;
    z-index: 100;         /* sits above page content */
    background: var(--ink);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .8rem 2rem;
  }
  .site-nav ul { display: flex; gap: 1.5rem; list-style: none; }
  .site-nav a { color: rgba(255,255,255,.7); text-decoration: none; transition: color .2s; }
  .site-nav a:hover { color: #fff; }

  /* ── Hero section with keyframe animation ── */
  @keyframes reveal {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  .hero {
    background: linear-gradient(135deg, #1a1714 0%, #2a6b6b 60%, #b5472a 100%);
    color: #fff;
    padding: 5rem 2rem 4rem;
    text-align: center;
  }
  .hero-kicker { animation: reveal .6s ease 0s both; }
  .hero h1     { animation: reveal .6s ease .15s both; }  /* staggered delay */
  .hero p      { animation: reveal .6s ease .30s both; }
  .hero-btns   { animation: reveal .6s ease .45s both; }

  /* ── Two-column CSS Grid page layout ── */
  .content-wrap {
    max-width: 980px;
    margin: 0 auto;
    padding: 3rem 2rem;
    display: grid;
    grid-template-columns: 2fr 1fr;       /* main gets 2/3, sidebar 1/3 */
    grid-template-areas: 'main side';
    gap: 2.5rem;
  }
  main  { grid-area: main; }
  aside { grid-area: side; }

  /* ── details/summary — native accordion, zero JS ── */
  details { border: 1px solid var(--rule); border-radius: var(--radius); margin-bottom: .6rem; }
  summary { padding: .75rem 1rem; cursor: pointer; font-weight: 600; }
  summary::after       { content: '+'; float: right; }  /* ::after pseudo-element */
  details[open] summary::after { content: '×'; }       /* [open] attribute selector */

  /* ── Contact form (CSS Grid layout) ── */
  .form-grid { display: grid; gap: 1rem; }
  .form-row  { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
  label      { display: flex; flex-direction: column; gap: .3rem; font-size: .85rem; }
  input, select, textarea {
    padding: .6rem .8rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    font-family: var(--font-sans);
    font-size: .9rem;
    transition: border-color .2s, box-shadow .2s;
  }
  input:focus, select:focus, textarea:focus {
    outline: none;                                    /* remove default ring */
    border-color: var(--accent2);
    box-shadow: 0 0 0 3px rgba(42,107,107,.15);       /* custom focus ring */
  }

  /* ── Responsive: collapse to 1 column on mobile ── */
  @media (max-width: 640px) {
    .site-nav ul { display: none; }                   /* hide nav links */
    .content-wrap {
      grid-template-columns: 1fr;
      grid-template-areas: 'main' 'side';
    }
    .form-row { grid-template-columns: 1fr; }           /* stack form fields */
  }
  </style>
</head>
<body>

<!-- ── STICKY NAV ── -->
<nav class="site-nav">
  <a href="#" class="logo">Hello World</a>
  <ul>
    <li><a href="#about">About</a></li>
    <li><a href="#accordion">FAQ</a></li>
    <li><a href="#contact">Contact</a></li>
  </ul>
</nav>

<!-- ── HERO ── -->
<header class="hero">
  <p class="hero-kicker">HTML5 + CSS &mdash; Getting Started</p>
  <h1>Hello, World.<br><em>Welcome to the web.</em></h1>
  <p>A complete beginner website…</p>
  <div class="hero-btns">
    <a href="#about"   class="btn btn-primary">Read the article</a>
    <a href="#contact" class="btn btn-outline">Get in touch</a>
  </div>
</header>

<!-- ── GRID LAYOUT ── -->
<div class="content-wrap">

  <main id="about">
    <article>
      <!-- <time> — machine-readable date -->
      <time datetime="2025-06-01">June 1, 2025</time>

      <h2>Building your first webpage from scratch</h2>
      <p>...article content...</p>

      <!-- <figure> wraps media + caption -->
      <figure>
        <img src="photo.jpg" alt="Descriptive alt text" loading="lazy">
        <figcaption>Caption for the image</figcaption>
      </figure>

      <!-- Native accordion — no JavaScript -->
      <details>
        <summary>Question text</summary>
        <p>Answer text</p>
      </details>

      <!-- Contact form -->
      <form id="contact" action="/submit" method="post" class="form-grid">

        <!-- 2-column name row -->
        <div class="form-row">
          <label>First name <input type="text"  name="first" required></label>
          <label>Last name  <input type="text"  name="last"  required></label>
        </div>

        <label>Email <input type="email" name="email" required></label>

        <label>Topic
          <select name="topic">
            <option value="">Choose...</option>
            <option>General question</option>
          </select>
        </label>

        <label>Message
          <textarea name="message" rows="4" required></textarea>
        </label>

        <!-- Checkbox with label -->
        <label>
          <input type="checkbox" name="consent" required>
          I agree to the privacy policy
        </label>

        <!-- <button> preferred over <input type="submit"> -->
        <button type="submit">Send message &rarr;</button>
      </form>
    </article>
  </main>

  <!-- SIDEBAR -->
  <aside>
    <h3>What to try next</h3>
    <!-- Description list — key:value pairs -->
    <dl>
      <dt>1. Edit the HTML</dt>
      <dd>Change the headline...</dd>
    </dl>
  </aside>

</div>

<!-- FOOTER — HTML entities for special chars -->
<footer>
  <p>
    &copy; 2025 Hello World
    &middot;               <!-- · -->
    Built with HTML5 &amp; CSS   <!-- & -->
  </p>
</footer>

</body>
</html>
↑ Back to top