From document skeleton to styled, responsive page — everything a working programmer needs.
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.
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>
<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.
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.
| Element | Role | Notes |
|---|---|---|
<header> | Page or section header | Can appear multiple times (once per section) |
<nav> | Navigation links | For major navigation blocks only |
<main> | Primary content | One per page; skip-to-content target |
<article> | Self-contained content | Blog post, news item, comment |
<section> | Thematic grouping | Should have a heading |
<aside> | Tangentially related content | Sidebar, pull-quote, ad |
<footer> | Page or section footer | Copyright, links, contact |
<figure> | Self-contained media | Image + caption pair |
<figcaption> | Caption for <figure> | First or last child of <figure> |
<time> | Date/time | datetime="2025-01-15" for machines |
<mark> | Highlighted/relevant text | Search-result highlight |
<details> / <summary> | Disclosure widget | Native accordion — no JS! |
<dialog> | Modal/popup | Native modal — JS to open/close |
<address> | Contact info | For 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>
<!-- 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>
<!-- 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>
<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>
<!-- 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>
These attributes are valid on any HTML element.
| Attribute | Purpose |
|---|---|
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 |
hidden | Hides 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 |
contenteditable | Makes element editable like a text field |
draggable | Enables 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"
| Entity | Character | Use when |
|---|---|---|
& | & | Ampersand in text / attributes |
< / > | < > | Angle brackets in content |
" | " | Quote inside an attribute |
' | ' | Apostrophe inside attribute |
| (non-breaking space) | Prevent line break between words |
© | © | Copyright symbol |
— | — | Em dash |
… | … | Ellipsis |
/* ── 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 */
| Selector | Matches | Example |
|---|---|---|
| Type / tag | All elements of that type | p { } |
| Class | Elements with that class | .card { } |
| ID | Element with that id (one per page) | #hero { } |
| Universal | Every element | * { } |
| Attribute | Has attribute | [disabled] { } |
| Attribute = | Exact value | [type="text"] { } |
| Attribute ^= | Value starts with | [href^="https"] { } |
| Attribute $= | Value ends with | [src$=".png"] { } |
| Attribute *= | Value contains | [class*="btn"] { } |
| Descendant | B anywhere inside A | nav a { } |
| Child > | Direct children only | ul > li { } |
| Adjacent + | Immediately following sibling | h1 + p { } |
| General ~ | All following siblings | h2 ~ p { } |
| :hover | Mouse over | a:hover { } |
| :focus | Keyboard / click focused | input:focus { } |
| :active | Being clicked | button:active { } |
| :nth-child(n) | nth child of parent | li:nth-child(odd) { } |
| :first-child / :last-child | First/last child | p:first-child { } |
| :not(x) | Doesn't match x | li:not(.active) { } |
| :is(x,y) | Matches x or y | :is(h1,h2,h3) { } |
| ::before / ::after | Generated pseudo-element | p::before { content:"» "; } |
| ::placeholder | Input placeholder text | input::placeholder { } |
| ::selection | User-selected text | ::selection { background:yellow; } |
| , | Group: apply to all | h1, h2, h3 { } |
When two rules conflict, the one with higher specificity wins. Specificity is a 3-digit weight: IDs · Classes / Attributes / Pseudo-classes · Elements / Pseudo-elements.
#nav a
= 1 ID, 0 class, 1 tag
.nav .link a
ul li a
!important) — use sparingly, breaks cascade./* 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; }
Every element is a rectangular box with four areas (outside → in):
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 */
}
| Value | Behaviour |
|---|---|
block | Starts on new line, takes full available width. <div>, <p>, headings |
inline | Flows in text line; width/height ignored. <span>, <a>, <strong> |
inline-block | Flows inline but respects width/height |
flex | Block-level flex container → children become flex items |
inline-flex | Same as flex but container is inline |
grid | Block-level grid container |
inline-grid | Same as grid but inline |
none | Removed from layout entirely (not just invisible) |
contents | Element itself disappears; children remain |
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 */
}
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) */
}
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;
}
/* ── 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"> */
.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 */
}
/* ── 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; }
rotate(360deg)
scale(1.15)
translateX
/* 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 */
CSS is mobile-first by convention: write base styles for small screens, then use min-width queries to layer in wider-screen styles.
/* ── 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) { }
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.
<iframe srcdoc="...">. Scroll down to read the fully annotated source, then copy it into your own index.html file.
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 — 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 →</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>
© 2025 Hello World
· <!-- · -->
Built with HTML5 & CSS <!-- & -->
</p>
</footer>
</body>
</html>
↑ Back to top