The foundation of every website on the open web is three languages: HTML for structure, CSS for presentation, and JavaScript for behaviour. Modern frameworks (React, Vue, Svelte) all compile down to those three. This post covers the basics that the advanced post and any future framework work assume — semantic HTML, the modern CSS layout systems (Flexbox + Grid), responsive design, vanilla JS DOM manipulation, jQuery (still found in older codebases), forms, and the accessibility minimum.


Learning Outcome

  • Write semantic, valid HTML5.
  • Lay out pages with Flexbox and CSS Grid.
  • Make a site responsive with media queries.
  • Manipulate the DOM with vanilla JavaScript and jQuery.
  • Build accessible forms and handle their submission.
  • Set up a minimal local development environment.

Chapter 1 — How the web works (one paragraph)

A browser sends an HTTP(S) request to a server; the server replies with an HTML document; the browser parses it, loads any linked CSS + JS + images, builds the DOM (the in-memory tree of elements) and CSSOM (the matched style tree), and renders pixels. JavaScript can later mutate the DOM and styles, triggering re-renders. Every "fancy" feature ultimately reduces to those moves.

Terminology

  • HTML — HyperText Markup Language; the structural language of web pages.
  • CSS — Cascading Style Sheets; selectors + property declarations that style HTML.
  • DOM — Document Object Model; the tree of nodes that the browser builds from HTML.
  • JavaScript (ES) / ECMAScript — the language that runs in the browser and (via Node.js) on servers.
  • Viewport — the visible area of the browser window.
  • Selector — a CSS pattern that targets HTML elements.

Chapter 2 — HTML essentials

Minimal HTML5 document

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello, web</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, web</h1>
<p>First page.</p>
<script src="app.js" defer></script>
</body>
</html>

Always:

  • <!doctype html> — first line.
  • lang="en" (or the right language) — accessibility + SEO.
  • <meta charset="utf-8"> — avoid mojibake.
  • <meta name="viewport" ...> — required for mobile responsiveness.
  • defer on <script> — runs after HTML parsed; almost always correct.

Semantic elements

Use the right element for the meaning, not just <div> everywhere:

Element Use
<header> top of a page or section
<nav> main navigation links
<main> unique page content (only one per page)
<section> thematic grouping
<article> self-contained piece (blog post, comment)
<aside> sidebar, callout
<footer> bottom of a page or section
<figure> + <figcaption> image with caption
<button> clickable action
<a href="..."> navigation

Semantics matter for accessibility (screen readers), SEO, and code readability. <button> is keyboard-accessible by default; a <div onclick="..."> is not.

Common elements quick-reference

1
2
3
4
5
6
7
8
9
10
11
12
<h1>Heading 1</h1> ... <h6>Heading 6</h6>
<p>Paragraph.</p>
<a href="https://example.com">link</a>
<strong>bold importance</strong> <em>emphasis</em>
<ul><li>unordered</li><li>list</li></ul>
<ol><li>ordered</li><li>list</li></ol>
<img src="photo.jpg" alt="A photo" width="600" height="400">
<table>
<thead><tr><th>Name</th><th>Age</th></tr></thead>
<tbody><tr><td>Ada</td><td>30</td></tr></tbody>
</table>

Trick: the alt attribute on <img> is not optional. Use a meaningful description; or alt="" if purely decorative. Empty alt tells screen readers to skip it.


Chapter 3 — CSS basics

Linking

1
2
3
4
<link rel="stylesheet" href="style.css">    <!-- external (preferred) -->
<style>p { color: navy; }</style> <!-- internal -->
<p style="color: navy;">inline</p> <!-- inline (avoid except for dynamic JS-set styles) -->

Selectors

1
2
3
4
5
6
7
8
9
10
p              { /* element */ }
.btn { /* class */ }
#main { /* id */ }
a:hover { /* pseudo-class */ }
li:nth-child(2n) { /* every even child */ }
input[type="email"] { /* attribute */ }
.card > h2 { /* direct child */ }
.card h2 { /* descendant */ }
.card + .card { /* adjacent sibling */ }

The cascade

When multiple rules match, the winner is decided by:

  1. Origin + importance — !important user styles → !important author styles → author styles → user → browser default.
  2. Specificity — inline (1000) → id (100) → class/attr/pseudo (10) → element (1).
  3. Source order — later wins on ties.

Avoid !important in normal code; it almost always means the cascade was lost. Refactor instead.

Box model

Every element has: content → padding → border → margin.

1
2
* { box-sizing: border-box; }   /* makes width include padding + border — saner default */

Set this globally in every project.

Useful units

Unit Meaning
px absolute pixels
rem multiple of the root font size (1rem = 16px by default)
em multiple of the parent's font size
% percentage of the parent
vw / vh percent of viewport width / height
clamp(min, val, max) bounded fluid value

rem for font sizes; px for tight UI details; % / vw for fluid layouts.


Chapter 4 — Flexbox

Flexbox is the right layout choice for one-dimensional arrangements (row or column).

1
2
3
4
5
6
7
8
9
10
11
.row {
display: flex;
gap: 1rem; /* spacing between children */
align-items: center; /* cross-axis alignment */
justify-content: space-between; /* main-axis */
}

.row > * {
flex: 1; /* equal-share growth */
}

Key parent properties:

Property Effect
flex-direction row / column / row-reverse / column-reverse
gap space between children
justify-content main-axis: start / end / center / space-between / space-around / space-evenly
align-items cross-axis alignment
flex-wrap nowrap (default) or wrap

Key child properties:

Property Effect
flex-grow grow ratio when extra space
flex-shrink shrink ratio when not enough
flex-basis preferred size before growing/shrinking
flex: 1 shorthand for 1 1 0 — equal share
align-self override align-items for one child

Chapter 5 — CSS Grid

Grid is the right choice for two-dimensional layouts (rows AND columns).

1
2
3
4
5
6
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
}

That one declaration creates a responsive image grid: as many columns as fit at minimum 220px each, the rest fluid.

Common patterns:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 12-column layout */
.container { display: grid; grid-template-columns: repeat(12, 1fr); gap: 1rem; }
.hero { grid-column: 1 / -1; }
.sidebar { grid-column: 1 / span 3; }
.main { grid-column: 4 / -1; }

/* named areas */
.layout {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 200px 1fr;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.footer { grid-area: footer; }

Trick: Grid for the page skeleton, Flexbox for the components inside it. Grid handles "where do regions sit on the page?". Flexbox handles "how do icons + text align inside this button?".


Chapter 6 — Responsive design

The mobile-first pattern: write base styles for small screens, add complexity at larger breakpoints with @media.

1
2
3
4
5
6
7
8
9
10
11
12
13
/* base: mobile */
.card { padding: 1rem; }

/* tablet up */
@media (min-width: 768px) {
.card { padding: 1.5rem; }
}

/* desktop up */
@media (min-width: 1024px) {
.card { padding: 2rem; max-width: 1100px; margin: 0 auto; }
}

Standard breakpoints (rough): 480 (large phone), 768 (tablet), 1024 (laptop), 1280+ (desktop).

Use container queries (@container) in modern browsers when component layout should depend on the container's width, not the viewport's.


Chapter 7 — JavaScript essentials

1
2
<script src="app.js" defer></script>

Variables

1
2
3
4
const PI = 3.14;          // never reassign — prefer const
let count = 0; // reassignable
// var: legacy; avoid

Types

number, string, boolean, null, undefined, object, array (object subtype), function (object subtype).

DOM manipulation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// query
const btn = document.querySelector('#go');
const all = document.querySelectorAll('.item');

// read / write
btn.textContent = 'Go!';
btn.classList.add('primary');
btn.setAttribute('data-id', '42');

// create + insert
const li = document.createElement('li');
li.textContent = 'new item';
document.querySelector('ul').append(li);

// events
btn.addEventListener('click', (e) => {
e.preventDefault();
console.log('clicked', e.target);
});

// fetch JSON
const resp = await fetch('/api/users');
const users = await resp.json();

async/await (ES2017+) is the modern way to handle promises — readable, error-handled like sync code.

Useful patterns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// guard
if (!btn) return;

// destructure
const { name, age } = user;
const [first, ...rest] = items;

// template literal
const msg = `Hello, ${name} — you are ${age}`;

// array helpers
const evens = nums.filter(n => n % 2 === 0);
const doubled = nums.map(n => n * 2);
const total = nums.reduce((a, b) => a + b, 0);
const has = nums.includes(5);

Chapter 8 — jQuery (still found in older codebases)

jQuery dominated 2008-2018. Modern browsers shrank its job — but you'll still see it in legacy code, WordPress themes, and many enterprise apps.

1
2
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// selector + chaining
$('#btn')
.text('Go!')
.addClass('primary')
.on('click', (e) => {
e.preventDefault();
$('#result').fadeIn(200).text('clicked');
});

// AJAX
$.getJSON('/api/users', users => {
users.forEach(u => $('<li>').text(u.name).appendTo('#users'));
});

Vanilla-JS equivalents:

jQuery Modern JS
$('.x') document.querySelectorAll('.x')
$.ajax / $.get / $.post fetch(url, options)
$(el).addClass('y') el.classList.add('y')
$(el).on('click', fn) el.addEventListener('click', fn)
$(el).fadeIn() CSS transitions on opacity

Trick: for new projects, skip jQuery. The vanilla DOM API is good enough since ~2017. For legacy projects, keep jQuery — re-platforming costs more than the bytes.


Chapter 9 — Forms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form action="/submit" method="post">
<label for="name">Name</label>
<input id="name" name="name" required minlength="2">

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

<label for="age">Age</label>
<input id="age" type="number" name="age" min="0" max="120">

<label><input type="checkbox" name="newsletter"> Subscribe</label>

<button type="submit">Send</button>
</form>

Browser-built validation:

  • required, minlength, maxlength, min, max, pattern, type="email", type="url".
  • The browser prevents submission and shows native messages.

JavaScript-side handling:

1
2
3
4
5
6
7
8
9
10
11
12
const form = document.querySelector('form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = new FormData(form); // or Object.fromEntries(data)
const resp = await fetch(form.action, {
method: form.method,
body: data,
});
if (!resp.ok) { /* show error */ return; }
/* success */
});

Chapter 10 — Accessibility minimum

Rule Why
Use semantic HTML screen readers + keyboard get correct affordances for free
<label for="id"> on every form input inputs need labels
Sufficient colour contrast (WCAG AA: 4.5:1 for text) low-vision users
:focus styles visible keyboard users must see where they are
alt on every image screen readers; SEO
Heading hierarchy (one h1, then h2, h3...) document outline
lang on <html> pronunciation, translation
Use aria-* sparingly — only when no semantic HTML fits "no ARIA is better than bad ARIA"
Test with keyboard only (Tab, Shift+Tab, Enter) the cheapest accessibility test

Run axe DevTools or Lighthouse for automated checks.


Chapter 11 — Development environment

Need Tool
Editor VS Code + Prettier + ESLint extensions
Local server npx serve or VS Code Live Server extension
Format Prettier — opinionated, drop-in
Lint JS ESLint
Browser Chrome / Firefox / Safari + DevTools
Source control Git

Open DevTools (F12 / Cmd+Opt+I) often:

  • Elements — inspect + edit live HTML/CSS.
  • Console — JS REPL, log output.
  • Network — every request, headers, timings.
  • Application — cookies, localStorage, service workers.
  • Lighthouse — performance / accessibility / SEO audit.

Practice Exercises

Exercise 1 — Personal page

Build a one-page personal site: header, hero, about, projects (cards), contact form. Use semantic HTML + a Grid skeleton + Flexbox inside components. Responsive at 480/768/1024.

Exercise 2 — Todo list (vanilla JS)

Static HTML; on form submit, append a new <li> with the entered text + a delete button. Persist to localStorage across reloads.

Skeleton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const form = document.querySelector('#new');
const list = document.querySelector('#list');

const save = (items) => localStorage.setItem('todos', JSON.stringify(items));
const load = () => JSON.parse(localStorage.getItem('todos') || '[]');

const render = () => {
list.innerHTML = '';
for (const [i, text] of load().entries()) {
const li = document.createElement('li');
li.textContent = text;
const x = document.createElement('button');
x.textContent = '×';
x.addEventListener('click', () => {
const items = load(); items.splice(i, 1); save(items); render();
});
li.append(x);
list.append(li);
}
};

form.addEventListener('submit', (e) => {
e.preventDefault();
const v = form.elements.text.value.trim();
if (!v) return;
const items = load(); items.push(v); save(items);
form.reset(); render();
});

render();

Exercise 3 — Fetch + render

Call https://jsonplaceholder.typicode.com/users with fetch; render each user as a card with name, email, city.

A grid of 12 images. At < 600px → 1 column. 600-900 → 2 cols. > 900 → 3 cols. Use Grid + minmax(...).

Exercise 5 — Form validation

A signup form (name, email, age, password). Use HTML5 attributes for native validation. Add a JS-side check that the password is ≥ 8 chars and contains a digit. Show errors below each input without losing focus.


Cheat-sheet

What How
HTML doctype <!doctype html>
Viewport (mobile) <meta name="viewport" content="width=device-width, initial-scale=1">
Semantic regions header / nav / main / section / article / aside / footer
Box sizing default * { box-sizing: border-box; }
1D layout Flexbox
2D layout Grid
Mobile-first media @media (min-width: 768px) { }
Modern variables const / let (avoid var)
Query DOM document.querySelector(...)
Class toggle el.classList.add / remove / toggle
Event el.addEventListener('click', fn)
HTTP await fetch(url) then await resp.json()
Async async/await
jQuery still useful? only in legacy / WordPress
Accessibility semantic HTML + label + alt + keyboard test
Audit Lighthouse in DevTools

What's next

The advanced post covers Node.js, package managers, modern build tools (Vite), the framework families (React / Vue / Svelte), CSS frameworks (Tailwind / Bootstrap), Three.js for 3D, API integration, and deployment.


References

Note: this post is in progress. Planned additions: CSS custom properties + theming, modern logical properties, transitions + animations, intersection observer, intl APIs.