Web Development (Basic) — HTML, CSS, JavaScript, jQuery
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.deferon<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
altattribute on<img>is not optional. Use a meaningful description; oralt=""if purely decorative. Emptyalttells 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:
- Origin + importance —
!importantuser styles →!importantauthor styles → author styles → user → browser default. - Specificity — inline (1000) → id (100) → class/attr/pseudo (10) → element (1).
- 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.
Exercise 4 — Responsive gallery
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
- MDN Web Docs — the canonical reference for every HTML / CSS / JS feature.
- web.dev — Google's modern practices guide.
- CSS-Tricks: A Complete Guide to Flexbox
- CSS-Tricks: A Complete Guide to Grid
- WCAG 2.1 quick reference
- Eloquent JavaScript by Marijn Haverbeke — free online; canonical JS book.
Note: this post is in progress. Planned additions: CSS custom properties + theming, modern logical properties, transitions + animations, intersection observer, intl APIs.










