Rendering the web cheatsheet
Resolving 98 modules across 13 phases
0%
Web cheatsheetBackend Engineering Guides
0 of 98 done
Focus mode on
Web cheatsheet98 modules13 phasesLive previews and full code

How websites work,from one tag to a full page

Start with a single HTML element, learn the three ways to add CSS and JavaScript, build media cards, navbars and heroes, assemble a full single page site, then see why Tailwind, Bootstrap, React and Next.js came up and ship it all in Docker.

Phase 00, module 00

Before you start

The web dictionary

Every word this guide uses, explained in plain words, in technical terms, and with an everyday comparison.

Module 00 of 97, phase 00

A word for everything

The web dictionary

A catalog of every keyword in this guide. Each entry says what the word means in plain language, what it means technically, and what it is like in everyday life, then links to the module that teaches it.

In detail

Use this module as a reference rather than a lesson. Filter by kind to see only HTML, CSS, JavaScript or tooling words, or type to find one. Tags, properties and functions in the code examples across this page link back to the module that explains them.

Client and server

Web
In plain words
Your browser asks, a computer somewhere answers.
Technically
The client sends HTTP requests; the server returns responses with status codes, headers and a body.
Learn it in module 01How the web works

URL

Web
In plain words
The full address of a page or file.
Technically
A string with scheme, host, optional port, path, query and fragment, parsed by the URL API.
Learn it in module 02URLs, domains and DNS

DNS

Web
In plain words
The internet's phone book.
Technically
The Domain Name System resolves a host name to an IP address through cached, hierarchical lookups.
Learn it in module 02URLs, domains and DNS

HTTP

Web
In plain words
The language browsers and servers use to talk.
Technically
A request response protocol with methods (GET, POST), status codes (200, 404) and headers; HTTPS adds TLS encryption.
Learn it in module 03HTTP requests, responses and status codes

Rendering

Web
In plain words
How the browser turns code into pixels.
Technically
Parsing HTML to the DOM and CSS to the CSSOM, building a render tree, then layout, paint and composite.
Learn it in module 04How browsers render a page

DevTools

Tooling
In plain words
The browser's built in toolbox for looking inside a page.
Technically
Panels for Elements, Console, Network, Performance and Application to inspect and debug a live page.
Learn it in module 05Browser developer tools

HTML

HTML
In plain words
The words and structure of a page.
Technically
HyperText Markup Language: nested elements written as tags that the browser parses into a document tree.
Learn it in module 07Anatomy of an HTML document

Attribute

HTML
In plain words
Extra settings written inside a tag.
Technically
A name value pair on an element, such as href, src, alt or class, readable from JavaScript.
Learn it in module 08Elements, attributes and nesting

Semantic HTML

HTML
In plain words
Using tags that say what content is, not how it looks.
Technically
Elements such as header, nav, main, article and footer that carry meaning for assistive tech and search engines.
Learn it in module 17Semantic layout elements

Form

HTML
In plain words
The part of a page where people type or choose things.
Technically
A form element grouping labelled controls whose values are submitted as name value pairs.
Learn it in module 15Forms and inputs

Meta tags

HTML
In plain words
Hidden notes in the head that describe the page.
Technically
Elements such as meta description, viewport and Open Graph tags read by browsers, search engines and social sites.
Learn it in module 18Meta tags, SEO and social cards

Accessibility

HTML
In plain words
Making pages work for everyone, including people using assistive tools.
Technically
Practices covering semantic markup, labels, focus order, contrast and ARIA so content is perceivable and operable.
Learn it in module 19Accessibility basics

CSS

CSS
In plain words
The rules that decide how a page looks.
Technically
Cascading Style Sheets: selectors paired with declarations applied through the cascade.
Learn it in module 20Inline, internal and external CSS

Inline, internal, external

CSS
In plain words
Three places to write styles or scripts: on the tag, in the page, or in a separate file.
Technically
A style attribute, a style or script element in the document, or a linked .css or .js file cached by the browser.
Learn it in module 20Inline, internal and external CSS

Selector

CSS
In plain words
The part of a CSS rule that picks which elements to style.
Technically
A pattern of types, classes, ids, attributes, combinators and pseudo classes matched against the DOM.
Learn it in module 21Selectors

Specificity

CSS
In plain words
The tie breaker when two rules want to style the same thing.
Technically
A weight computed from ids, classes and types; higher weight wins, and later rules win ties.
Learn it in module 22Specificity, the cascade and inheritance

Cascade

CSS
In plain words
How the browser decides which style finally applies.
Technically
The algorithm ordering declarations by origin, importance, layer, specificity and source order.
Learn it in module 22Specificity, the cascade and inheritance

Box model

CSS
In plain words
Every element is a box with stuffing, padding, a border and space around it.
Technically
Content, padding, border and margin, sized by box-sizing content-box or border-box.
Learn it in module 23The box model

Units

CSS
In plain words
The measurements CSS uses, like pixels or percentages.
Technically
Absolute (px), relative (em, rem, %), viewport (vw, vh, dvh) and container (cqi) lengths.
Learn it in module 24Units and sizing

Flexbox

CSS
In plain words
A way to line things up in a row or a column.
Technically
A one dimensional layout model controlled with justify-content, align-items, gap and flex on children.
Learn it in module 31Flexbox

Grid

CSS
In plain words
A way to lay things out in rows and columns at once.
Technically
A two dimensional layout model with tracks, fr units, template areas and spanning items.
Learn it in module 32CSS Grid

Media query

CSS
In plain words
Styles that only apply on certain screens or settings.
Technically
An @media rule testing width, orientation, prefers-color-scheme or prefers-reduced-motion.
Learn it in module 30Responsive design and media queries

Custom property

CSS
In plain words
A named value you can reuse across styles.
Technically
A CSS variable such as --accent declared on a selector, inherited, and read with var().
Learn it in module 29Custom properties (CSS variables)

Dark mode

CSS
In plain words
A darker colour scheme that is easier on the eyes at night.
Technically
Swapping token values under prefers-color-scheme or a data-theme attribute, with color-scheme set for controls.
Learn it in module 37Dark mode and theming

Transition and animation

CSS
In plain words
Smooth change from one look to another.
Technically
transition interpolates property changes; @keyframes defines multi step animations run by animation.
Learn it in module 35Transitions and transforms

defer and async

JavaScript
In plain words
Ways to load scripts without freezing the page.
Technically
defer runs after parsing in order; async runs as soon as it downloads; type=module defers by default.
Learn it in module 39Inline, internal and external JavaScript

Variable

JavaScript
In plain words
A named box that holds a value.
Technically
A binding declared with const or let, block scoped, holding primitives or references.
Learn it in module 40Variables and data types

Function

JavaScript
In plain words
A reusable set of steps with a name.
Technically
A callable object with parameters, a body and a return value; arrow functions keep the outer this.
Learn it in module 42Functions, scope and closures

Array and object

JavaScript
In plain words
Lists of things and bundles of named things.
Technically
Arrays are ordered, index based collections; objects map string keys to values.
Learn it in module 43Arrays, objects and JSON

Class

JavaScript
In plain words
A blueprint for making similar objects.
Technically
Syntax over prototypes with a constructor, methods, fields and inheritance via extends.
Learn it in module 45Classes, objects and this

Module

JavaScript
In plain words
A file that shares only what it chooses to.
Technically
An ES module using import and export, loaded with type=module and scoped to itself.
Learn it in module 46ES modules: import and export

Promise and async/await

JavaScript
In plain words
A way to wait for slow work without stopping everything else.
Technically
A Promise represents a future value; await pauses an async function until it settles.
Learn it in module 47Async code, promises and async await

DOM

DOM
In plain words
The live, editable version of the page that JavaScript sees.
Technically
The Document Object Model: a tree of nodes with methods to query, create, change and remove elements.
Learn it in module 49The DOM and selecting elements

querySelector

DOM
In plain words
Finds an element on the page using a CSS selector.
Technically
document.querySelector returns the first matching element or null; querySelectorAll returns a NodeList.
Learn it in module 49The DOM and selecting elements

Event

DOM
In plain words
Something that happened, like a click or a key press.
Technically
An Event object dispatched to targets and bubbling up; handled with addEventListener.
Learn it in module 51Events and event delegation

Event delegation

DOM
In plain words
One listener on a parent handles clicks for many children.
Technically
Using bubbling and event.target.closest() to handle events from dynamic children at a common ancestor.
Learn it in module 51Events and event delegation

fetch

DOM
In plain words
Asks a server for data without reloading the page.
Technically
A promise based API that sends HTTP requests and returns a Response to read as JSON or text.
Learn it in module 53Fetch API and JSON

JSON

DOM
In plain words
A plain text format for sending data.
Technically
JavaScript Object Notation, parsed with JSON.parse and produced with JSON.stringify.
Learn it in module 53Fetch API and JSON

Card

Component
In plain words
A boxed summary with a picture, title and action.
Technically
A reusable component, usually an article with media, heading, text and a link stretched over it.
Learn it in module 57Media items and card components

Hamburger menu

Component
In plain words
The three line button that opens a menu on small screens.
Technically
A button with aria-expanded controlling a navigation panel shown and hidden by JavaScript.
Learn it in module 58Navbar and hamburger menu

Hero section

Component
In plain words
The big welcome area at the top of a page.
Technically
A first viewport section with headline, supporting text, actions and often an illustration.
Learn it in module 59Hero sections

Modal dialog

Component
In plain words
A box that pops up over the page and asks for attention.
Technically
The dialog element opened with showModal, trapping focus and closing on Escape.
Learn it in module 61Modals with the dialog element

Parallax

Component
In plain words
Layers that move at different speeds as you scroll.
Technically
Translating elements by a fraction of scroll position to create depth, often with smooth scrolling.
Learn it in module 69Lenis smooth scrolling and parallax

Sass

Library
In plain words
CSS with extra powers like nesting and loops.
Technically
A preprocessor compiling SCSS with variables, mixins, partials and functions into CSS.
Learn it in module 72Sass and preprocessors

Tailwind CSS

Library
In plain words
Styling by stacking small ready made classes in your HTML.
Technically
A utility first framework generating only the classes you use, configured with design tokens.
Learn it in module 73Tailwind CSS (utility first)

Bootstrap

Library
In plain words
A ready made kit of styled components and a grid.
Technically
A component framework with a 12 column grid, utilities and JavaScript plugins, themed through Sass variables.
Learn it in module 74Bootstrap and component frameworks

React

Framework
In plain words
A library for building pages from reusable pieces that update themselves.
Technically
A UI library where components are functions of props and state, rendered through a virtual DOM.
Learn it in module 79React components, JSX and props

Component

Framework
In plain words
A self contained piece of UI you can reuse.
Technically
A function returning JSX that receives props and may hold state with hooks.
Learn it in module 79React components, JSX and props

State and hooks

Framework
In plain words
Data that, when it changes, makes the screen change.
Technically
useState stores component state; useEffect syncs with outside systems after render.
Learn it in module 80State and effects with hooks

Next.js

Framework
In plain words
React with routing, server rendering and deployment built in.
Technically
A React framework with file based routing, server components, data fetching and image optimisation.
Learn it in module 82Why Next.js

SSR, SSG, CSR

Framework
In plain words
Where and when the page HTML gets built.
Technically
Server side rendering per request, static generation at build time, or client side rendering in the browser.
Learn it in module 84CSR, SSR, SSG and ISR

npm

Tooling
In plain words
The app store for JavaScript code libraries.
Technically
The Node package registry and CLI that installs dependencies listed in package.json.
Learn it in module 86npm, packages and package.json

Bundler

Tooling
In plain words
A tool that packs your files into fewer, faster ones.
Technically
Vite or webpack resolving imports, transforming code and outputting optimised assets with a dev server.
Learn it in module 87Vite and bundlers

Git

Tooling
In plain words
A time machine for your code.
Technically
A distributed version control system tracking commits, branches and merges.
Learn it in module 90Git and GitHub

Core Web Vitals

Tooling
In plain words
Scores that measure how fast and stable a page feels.
Technically
LCP for loading, INP for responsiveness and CLS for visual stability, measured in the field.
Learn it in module 91Web performance and Core Web Vitals

Docker

Tooling
In plain words
A box that packs your site and everything it needs to run anywhere.
Technically
A container image built from a Dockerfile, often serving static files with nginx.
Learn it in module 93Containerising a website with Docker

Phase 01, modules 01 to 06

How the web works

Browsers, servers and the trip in between

What happens between typing an address and seeing a page, and the tools you use to watch it happen.

Module 01 of 97, phase 01

The trip a page takes

How the web works

A browser asks a server for a file over the internet, the server answers, and the browser turns the answer into the page you see. Everything else in web development builds on that loop.

Module 02 of 97, phase 01

Addresses and phone books

URLs, domains and DNS

A URL tells the browser which protocol, which server and which file to ask for. DNS turns the human friendly domain into the numeric IP address of that server.

In detail

Read a URL left to right: scheme (https), host (example.com), optional port, path (/blog/post), query (?page=2) and fragment (#comments). The fragment never reaches the server; the browser uses it to scroll. DNS lookups are cached at several levels, which is why a changed record can take time to reach everyone.

url-anatomy.js
JavaScript
const url = new URL("https://shop.example.com:8443/blog/post?page=2#comments");url.hostname; // "shop.example.com"url.searchParams.get("page"); // "2"
Why it matters URL and URLSearchParams handle encoding for you, so an ampersand inside a value cannot break the query.

Module 03 of 97, phase 01

Ask nicely, get an answer

HTTP requests, responses and status codes

HTTP is the language browsers and servers speak. A request has a method, a path and headers; a response has a status code, headers and a body.

In detail

GET reads, POST creates, PUT and PATCH update, DELETE removes. Status codes group by their first digit: 2xx success, 3xx redirect, 4xx the client made a mistake, 5xx the server failed. Headers carry everything else: content type, caching rules, cookies and security policies.

request.http
Shell
GET /blog/post HTTP/1.1Host: example.comAccept: text/html
Why it matters The blank line separates headers from the body. Everything before it is metadata about the message.

Module 04 of 97, phase 01

From text to pixels

How browsers render a page

The browser parses HTML into the DOM, CSS into the CSSOM, combines them into a render tree, calculates layout, then paints pixels and composites layers.

Module 05 of 97, phase 01

X-ray vision for pages

Browser developer tools

Every browser ships developer tools: an Elements panel to inspect and edit the DOM and CSS live, a Console for JavaScript, a Network panel for requests, and panels for performance and accessibility.

In detail

Press F12 or Ctrl Shift I. Edit a style in Elements and see it instantly, read errors in Console, and filter Network by type to see what loaded, how big it was and how long it took. Lighthouse audits performance, accessibility and SEO in one click. Device mode previews phones and slow networks.

console-tricks.js
JavaScript
console.table([{ name: "Ada", role: "admin" }]);$0.style.outline = "2px solid red"; // $0 is the selected element
Why it matters console.table and console.time turn guessing into reading. They exist in every modern browser.

Module 06 of 97, phase 01

A desk ready for work

Editor and project setup

A code editor, a folder with index.html, styles.css and script.js, and a live preview server are all you need to start building.

In detail

VS Code with the Live Server or Live Preview extension reloads the page on every save. Keep files lowercase with hyphens, put images in an assets folder, and always open the page through a local server rather than the file system, because modules and fetch do not work from file URLs.

my-first-site

  • my-first-site
  • index.htmlhome page, the entry point
  • about.htmla second page
  • css
  • style.cssexternal stylesheet
  • js
  • app.jsloaded with defer
  • assets
  • hero.webpoptimised image
  • logo.svgscalable logo
  • favicon.ico
  • README.mdwhat the project is

Lowercase names with hyphens avoid broken links on case sensitive servers.

terminal
Shell
mkdir my-site && cd my-sitetouch index.html styles.css script.jsnpx serve .
Why it matters A local server mirrors how a real host serves files, so relative paths, modules and fetch behave correctly.

Phase 02, modules 07 to 19

The skeleton

HTML foundations

Documents, elements, media, forms and the semantic structure every page is built on.

Module 07 of 97, phase 02

The page's birth certificate

Anatomy of an HTML document

Every page starts with a doctype, an html element with a language, a head for information about the page, and a body for what people see.

In detail

The doctype switches the browser to standards mode. The head holds the character set, viewport, title, description and links to CSS. The body holds content. Indent nested elements, close every element that needs closing, and validate with the W3C validator when something looks wrong.

index.html
HTML
<!doctype html><html lang="en">  <head><meta charset="utf-8"><title>My page</title></head>  <body><h1>Hello web</h1></body></html>
Why it matters The viewport meta tag is what makes a page responsive on phones. Without it, mobile browsers render a zoomed out desktop layout.

Module 08 of 97, phase 02

Words in labelled boxes

Elements, attributes and nesting

An element is an opening tag, content and a closing tag. Attributes inside the opening tag add information such as an id, a class, a link target or alternative text.

In detail

Void elements such as img, input, br and meta have no closing tag. Elements nest like boxes inside boxes and must close in reverse order. Global attributes work on any element: id must be unique, class can repeat, hidden hides, lang sets language, and data-* stores your own values for scripts.

elements.html
HTML
<p class="note" id="intro" data-level="beginner">Read <a href="/guide">the guide</a>.</p><img src="cat.jpg" alt="A cat asleep on a keyboard" />
Why it matters data-* attributes are the clean bridge between HTML and JavaScript. Scripts read them with element.dataset.

Module 09 of 97, phase 02

Say what you mean

Headings, paragraphs and text semantics

Headings h1 to h6 build an outline, paragraphs hold prose, and inline elements such as strong, em, code, abbr and time add meaning that browsers, search engines and screen readers understand.

In detail

Use one h1 per page and never skip levels just for size; style with CSS instead. strong means importance, em means emphasis, b and i are only visual. blockquote and cite quote sources, pre keeps whitespace, and time carries a machine readable date.

article.html
HTML
<h2>Why semantics matter</h2><p>Search engines read <strong>meaning</strong>, not just <em>looks</em>.</p>
Why it matters Screen reader users jump between headings to scan a page. A skipped level breaks that outline.

Module 11 of 97, phase 02

Pictures that load smart

Images, figures and responsive images

The img element shows an image with alternative text. srcset, sizes and picture let the browser choose the right file for each screen and format.

In detail

Always write alt text that describes purpose, or alt="" for decoration. Set width and height so the page does not jump while loading. Serve modern formats such as AVIF and WebP through picture with a JPEG fallback, and add loading="lazy" for images below the fold.

Live previewrendered from the code below
figure.html
HTML
<figure>  <img src="lake.jpg" alt="A calm lake at sunrise" width="800" height="533" loading="lazy" />  <figcaption>Sunrise over the lake</figcaption></figure>
Why it matters sizes tells the browser how wide the image will display, so it can pick the smallest file from srcset that still looks sharp.

Module 12 of 97, phase 02

Sound and motion built in

Audio, video and embeds

The video and audio elements play media natively with controls, captions and multiple sources. iframe embeds other pages such as maps and players.

In detail

Give video a poster, provide WebM and MP4 sources, and add a track element with captions. Autoplay only works muted. For iframes set a title for accessibility, loading="lazy" and a sandbox when the content is not yours.

media.html
HTML
<video controls width="640" poster="intro.jpg">  <source src="intro.webm" type="video/webm" />  <source src="intro.mp4" type="video/mp4" /></video>
Why it matters preload="metadata" loads only the duration and first frame, so a page with several videos stays fast.

Module 13 of 97, phase 02

Things in order, or not

Lists

Unordered lists group items without order, ordered lists number them, and description lists pair terms with explanations.

In detail

Navigation menus, card grids and feature lists are usually ul elements styled with CSS, because a screen reader announces how many items there are. ol accepts start and reversed. dl suits glossaries and key value details.

lists.html
HTML
<ul><li>HTML</li><li>CSS</li><li>JavaScript</li></ul><ol><li>Write</li><li>Test</li><li>Ship</li></ol>
Why it matters A list is announced with its length, which helps people know how much is coming before they listen to it all.

Module 14 of 97, phase 02

Rows and columns that mean something

Tables for tabular data

Tables present data in rows and columns with headers, a caption and optional groups. They are for data, never for page layout.

In detail

Use thead, tbody and tfoot, mark header cells with th and a scope, and add a caption that describes the table. Two tone rows with nth-child make long tables easy to follow, and wrapping the table in a scrollable container keeps it usable on phones.

Live previewrendered from the code below
pricing-table.html
HTML
<table>  <caption>Plans</caption>  <thead><tr><th scope="col">Plan</th><th scope="col">Price</th></tr></thead>  <tbody><tr><th scope="row">Free</th><td>0</td></tr></tbody></table>
Why it matters scope tells assistive technology whether a header labels a column or a row, so each cell is read with the right header.

Module 15 of 97, phase 02

Asking people for things

Forms and inputs

Forms collect input with labelled controls and send it to a server or to JavaScript. HTML gives you text, email, number, date, checkbox, radio, select, file and more for free.

In detail

Every input needs a label linked by for and id. Group related controls with fieldset and legend. The type attribute changes the mobile keyboard and adds built in checks, and autocomplete hints let browsers fill details for people.

Live previewrendered from the code below
signup.html
HTML
<label for="email">Email</label><input id="email" name="email" type="email" autocomplete="email" required />
Why it matters Clicking a label focuses or toggles its control, which doubles the click target for touch users.

Module 16 of 97, phase 02

Catch mistakes before they leave

Built in form validation

HTML validates many inputs without JavaScript: required, type, min, max, minlength, maxlength and pattern. CSS can style valid and invalid states.

In detail

Native validation blocks submit and shows a message in the person's language. Style errors with :user-invalid so fields only turn red after someone interacts. Server side validation is still required, because anyone can bypass the browser.

validation.html
HTML
<input type="text" name="pin" pattern="[0-9]{6}" title="Six digits" required />
Why it matters inputmode="numeric" shows a number pad on phones while keeping the value a string, which suits codes that can start with zero.

Module 17 of 97, phase 02

Rooms with names

Semantic layout elements

header, nav, main, article, section, aside and footer describe the regions of a page. They look like div by default but carry meaning.

In detail

Landmarks let screen reader users jump to the main content or navigation instantly, and they help search engines understand structure. Use one main per page, article for self contained content such as a blog post, section for a themed group with a heading, and div only when no meaningful element fits.

layout.html
HTML
Why it matters The skip link is the first thing keyboard users reach. It lets them bypass the navigation on every page.

Module 18 of 97, phase 02

Be found, be shared

Meta tags, SEO and social cards

The head carries metadata for search engines and social apps: title, description, canonical URL, Open Graph and Twitter cards, and structured data in JSON-LD.

In detail

Write a unique title under about sixty characters and a description under about one hundred sixty. Open Graph controls the preview card when a link is shared. JSON-LD describes the page as an article, product or FAQ so search results can show rich snippets.

head-seo.html
HTML
<title>Learn CSS Grid in 10 minutes | Web Guide</title><meta name="description" content="A visual, beginner friendly tour of CSS Grid." /><meta property="og:image" content="https://example.com/og/grid.png" />
Why it matters canonical tells search engines which URL is the original when the same page is reachable at several addresses.

Module 19 of 97, phase 02

A page for everyone

Accessibility basics

Accessible pages work for keyboard users, screen reader users, people who zoom, and people with low vision or motion sensitivity. Most of it comes from correct HTML.

In detail

Use real buttons and links, label every control, keep visible focus, give images alt text, and keep text contrast at least 4.5 to 1. Reach for ARIA only when no native element exists, and respect prefers-reduced-motion for animation.

a11y.html
HTML
<button type="button" aria-expanded="false" aria-controls="menu">Menu</button><img src="chart.png" alt="Sales rose 20 percent from June to July" />
Why it matters aria-expanded tells assistive technology whether the menu is open. Update it in JavaScript whenever the menu toggles.

Phase 03, modules 20 to 30

The skin

CSS foundations

Three ways to add styles, how the cascade decides, and the box every element lives in.

Module 20 of 97, phase 03

Three doors for style

Inline, internal and external CSS

CSS can live in a style attribute on one element, in a style element in the head, or in an external stylesheet linked with link. External files are the default for real sites.

In detail

Inline styles win almost every cascade fight and cannot use media queries or hover, so keep them for values set by JavaScript. Internal styles suit single page demos and critical CSS. External stylesheets are cached by the browser and shared across pages, which makes them fastest after the first visit.

Three ways to add CSS to a page
WayWhere it livesCascade strengthCachingBest for
Inlinestyle="color: red" on one tagBeats every selector except !importantNone, repeated in every pageValues set by JavaScript
Internal<style> inside the headNormal, by specificity and orderOnly with the HTML pageCritical CSS, single page demos
External<link rel="stylesheet" href="style.css">Normal, by specificity and orderCached once, shared by every pageAlmost everything
@importInside another stylesheetNormalExtra request, delays renderingAvoid in production; use a bundler
3 examples
inline
HTML
<p style="color: #1d63ed; font-weight: 600">Inline style</p>
Why it matters Inline styles are hard to override and impossible to reuse. Keep them for dynamic values.

Module 21 of 97, phase 03

Pointing at the right things

Selectors

Selectors choose which elements a rule styles: by type, class, id, attribute, relationship and state.

In detail

Class selectors are the workhorse. Combinators select by relationship: a space for any descendant, > for direct children, + for the next sibling and ~ for later siblings. Attribute selectors match patterns such as links ending in .pdf. :is() and :where() group selectors, and :where() adds no specificity.

selectors.css
CSS
.card > h3 { margin: 0; }a[href$=".pdf"]::after { content: " (PDF)"; }
Why it matters Prefer classes over ids for styling. Ids carry heavy specificity and make later overrides painful.

Module 22 of 97, phase 03

Who wins the argument

Specificity, the cascade and inheritance

When several rules target the same element, the cascade decides: origin and importance first, then cascade layers, then specificity, then source order.

In detail

Specificity is counted as ids, classes and attributes, then types: #nav .link scores 1, 1, 0. Later rules win ties. Some properties such as color and font inherit from parents; others such as margin and border do not. @layer lets you order whole groups of styles so a framework's rules never beat yours by accident.

How selectors are scored (ids, classes, types)
SelectorIdsClasses, attributes, pseudo classesTypes, pseudo elementsScore
p0010, 0, 1
.card p0110, 1, 1
nav a:hover0120, 1, 2
input[type="email"]0110, 1, 1
#menu .link1101, 1, 0
style="..."inlineBeats all of the above
:where(.card) p0010, 0, 1 (where adds nothing)
cascade.css
CSS
@layer reset, base, components;@layer components { .btn { color: white; } }
Why it matters Layers order is decided before specificity, so a simple selector in a later layer still beats a complex one in an earlier layer.

Module 23 of 97, phase 03

Every element is a box

The box model

Every element is a rectangle made of content, padding, border and margin. box-sizing decides whether width includes padding and border.

In detail

With the default content-box, width: 200px plus 20px padding makes a 240px wide box. Setting box-sizing: border-box everywhere makes widths predictable. Vertical margins between blocks collapse into the larger one, which surprises everyone once; gap in flex and grid avoids it.

box.css
CSS
*, *::before, *::after { box-sizing: border-box; }.card { width: 300px; padding: 20px; border: 2px solid; margin: 16px; }
Why it matters outline draws outside the border without changing layout, which is why it is the right tool for focus rings.

Module 24 of 97, phase 03

Measuring in the right ruler

Units and sizing

CSS has absolute units such as px and relative units such as rem, em, %, vw, vh, dvh, ch and fr. Relative units make layouts that respect user settings and screen sizes.

In detail

Use rem for font sizes and spacing so everything scales when someone increases their default font. Use % and fr for fluid widths, ch to limit line length, and dvh for full height sections on mobile where browser bars come and go. min(), max() and clamp() combine units into responsive values.

units.css
CSS
h1 { font-size: clamp(2rem, 5vw, 4rem); }.prose { max-width: 65ch; }
Why it matters clamp(min, preferred, max) replaces whole sets of media queries for font sizes and spacing.

Module 25 of 97, phase 03

Colour and voice

Colours, fonts and typography

Colours can be written as hex, rgb, hsl or modern oklch, and fonts are set with font-family stacks, web fonts and properties for size, weight, line height and spacing.

In detail

oklch gives perceptually even colour steps, and color-mix() derives tints from a brand colour. Load web fonts with @font-face and font-display: swap so text shows immediately. Good body text has a line height around 1.5 and a comfortable measure.

type.css
CSS
body { font: 400 1rem/1.6 "Inter", system-ui, sans-serif; color: oklch(25% 0.02 260); }.tint { background: color-mix(in srgb, #1d63ed 12%, white); }
Why it matters text-wrap: balance evens out heading lines so a single word never sits alone on the last line.

Module 26 of 97, phase 03

How boxes line up by default

Display and normal flow

Normal flow stacks block elements vertically and lines inline elements up like words. The display property changes that: block, inline, inline-block, flex, grid, none and contents.

In detail

Block boxes take the full width and start on a new line; inline boxes ignore width and vertical margin. inline-block sits in a line but accepts size. display: none removes an element from layout and the accessibility tree, while visibility: hidden only hides it.

display.css
CSS
.tag { display: inline-block; padding: 2px 8px; }.hidden { display: none; }
Why it matters display: contents is handy for grid and flex layouts, but test it with a screen reader before using it on buttons or lists.

Module 27 of 97, phase 03

Pinning things in place

Positioning and z-index

position: relative, absolute, fixed and sticky move elements out of normal flow or pin them, and z-index decides which overlapping element is on top.

In detail

absolute positions relative to the nearest positioned ancestor, so give the parent position: relative. fixed sticks to the viewport, sticky sticks within its scroll container once you scroll past it. z-index only compares elements in the same stacking context; transforms and opacity create new contexts, which explains most z-index mysteries.

position.css
CSS
.card { position: relative; }.badge { position: absolute; top: 8px; right: 8px; }.header { position: sticky; top: 0; z-index: 10; }
Why it matters inset: 0 is shorthand for all four offsets and is the quickest way to cover a parent or the screen.

Module 28 of 97, phase 03

States and ghosts

Pseudo classes and pseudo elements

Pseudo classes style states such as :hover, :focus-visible, :checked and :nth-child. Pseudo elements such as ::before, ::after and ::placeholder style parts that are not elements.

In detail

Use :focus-visible for keyboard focus rings, :nth-child(even) for two tone rows, :not() to exclude, and :empty to hide empty containers. ::before and ::after need a content property and are perfect for decorative icons, quotes and overlays.

pseudo.css
CSS
tr:nth-child(even) { background: #eef4ff; }.btn:focus-visible { outline: 3px solid #1d63ed; }
Why it matters ::before and ::after are invisible to most screen readers when content is decorative, so never put essential text in them.

Module 29 of 97, phase 03

Variables for your design

Custom properties (CSS variables)

Custom properties store values such as colours and spacing under names like --brand, and var() reads them. They cascade and inherit, so changing one value at the top updates everything.

In detail

Define design tokens on :root, override them on a component or under a theme attribute, and update them from JavaScript with style.setProperty. Unlike Sass variables they exist at runtime, which is what makes live theming and dark mode simple.

tokens.css
CSS
:root { --brand: #1d63ed; --radius: 12px; }.btn { background: var(--brand); border-radius: var(--radius); }
Why it matters A variant that only changes a token keeps every other rule shared, so buttons cannot drift apart over time.

Module 30 of 97, phase 03

One page, every screen

Responsive design and media queries

Responsive design adapts one page to every screen with fluid layouts, flexible media and media queries that change rules at breakpoints.

In detail

Write mobile first: base styles for small screens, then min-width queries add complexity. Choose breakpoints where your content breaks, not at device widths. Media queries also read user preferences: prefers-color-scheme, prefers-reduced-motion and hover capability.

responsive.css
CSS
.grid { display: grid; gap: 16px; }@media (min-width: 700px) { .grid { grid-template-columns: repeat(3, 1fr); } }
Why it matters repeat(auto-fit, minmax(260px, 1fr)) builds a responsive card grid with zero media queries.

Phase 04, modules 31 to 38

Layout and motion

Modern CSS layout and animation

Flexbox, grid, container queries, motion, theming and ways to keep big stylesheets sane.

Module 31 of 97, phase 04

Lining things up in a row

Flexbox

Flexbox lays items out along one axis, a row or a column, and controls how they grow, shrink, wrap and align.

In detail

Set display: flex on the parent. justify-content aligns along the main axis, align-items across it, and gap spaces items. On children, flex: 1 shares leftover space and flex-wrap lets items move to the next line. Flexbox is ideal for navbars, toolbars, button groups and centering.

justify-content

align-items

123
Live previewrendered from the code below
flex.css
CSS
.toolbar { display: flex; align-items: center; gap: 12px; }.toolbar .spacer { flex: 1; }
Why it matters gap replaces margin hacks between items and never adds space at the outer edges.

Module 32 of 97, phase 04

Rows and columns at once

CSS Grid

Grid lays items out in two dimensions with rows and columns you define, then places items by line numbers or named areas.

In detail

grid-template-columns: repeat(3, 1fr) makes three equal columns. grid-template-areas draws a layout with words. Items can span with grid-column: span 2. subgrid lets nested items align to the parent grid. Use grid for page layouts and card galleries, flexbox for one dimensional rows.

grid-template-columns: 200px 1fr 1fr;
grid-template-areas:
  "header  header header"
  "sidebar main   main"
  "footer  footer footer";
Live previewrendered from the code below
grid.css
CSS
.page { display: grid; grid-template-areas: "header header" "nav main" "footer footer"; grid-template-columns: 220px 1fr; }
Why it matters Rewriting grid-template-areas in one media query rearranges the whole page without touching the HTML order.

Module 33 of 97, phase 04

Components that know their size

Container queries

Container queries style a component based on the width of its container, not the screen, so the same card can be compact in a sidebar and wide in the main column.

In detail

Mark a parent with container-type: inline-size and an optional container-name, then write @container rules. Container query units such as cqi size things relative to the container. This is the missing piece that makes truly reusable components possible.

card-cq.css
CSS
.card-wrap { container-type: inline-size; }@container (min-width: 420px) { .card { grid-template-columns: 160px 1fr; } }
Why it matters The same card markup now works in a narrow sidebar and a wide feed without a single media query.

Module 34 of 97, phase 04

New superpowers in plain CSS

Nesting, :has(), clamp and logical properties

Modern CSS supports nesting without a preprocessor, :has() as a parent selector, logical properties for any writing direction, and accent-color, aspect-ratio and scroll snapping out of the box.

In detail

Nesting with & keeps related rules together. :has() selects an element based on what it contains, such as a card that has an image. margin-inline and padding-block adapt to right to left languages. Check caniuse before relying on the newest features and provide a sensible fallback.

modern.css
CSS
.card {  &:hover { scale: 1.02; }  &:has(img) { padding-top: 0; }}
Why it matters :has() removes most of the JavaScript that used to toggle parent classes based on child state.

Module 35 of 97, phase 04

Smooth changes

Transitions and transforms

Transitions animate a property from one value to another when it changes, and transforms move, scale and rotate elements without affecting layout.

In detail

Animate transform and opacity whenever possible because the browser can run them on the compositor without recalculating layout. Use individual properties translate, scale and rotate for readability, and pick easing such as ease-out for entrances.

Live previewrendered from the code below
hover-card.css
CSS
.card { transition: transform 0.25s ease, box-shadow 0.25s ease; }.card:hover { transform: translateY(-4px) scale(1.02); }
Why it matters Listing exact properties in transition avoids accidentally animating layout properties that cause jank.

Module 36 of 97, phase 04

Motion with a script

Keyframe animations

@keyframes define the steps of an animation, and the animation property plays them with a duration, delay, easing, direction and repeat count.

In detail

Use keyframes for loaders, attention pulses, floating illustrations and entrances. Negative delays start an animation partway through, which staggers many items without JavaScript. Scroll driven animations with animation-timeline: view() now link progress to scroll in supporting browsers.

Live previewrendered from the code below
float.css
CSS
@keyframes float { 50% { translate: 0 -10px; } }.bubble { animation: float 4s ease-in-out infinite; }
Why it matters Negative animation delays make each item start mid animation, so a row of icons bobs out of sync without extra code.

Module 37 of 97, phase 04

Two moods, one stylesheet

Dark mode and theming

Theming swaps a small set of custom properties. prefers-color-scheme follows the system, and a data-theme attribute lets people choose for themselves.

In detail

Define light tokens on :root, dark tokens under the media query and under [data-theme="dark"], and make every component use tokens only. Set color-scheme so form controls and scrollbars match. Dark mode is not inverted colours: lower contrast slightly, lift surfaces with lighter greys, and desaturate brand colours.

theme.css
CSS
:root { --bg: #fff; --ink: #111; }:root[data-theme="dark"] { --bg: #0b1220; --ink: #e8eefc; }body { background: var(--bg); color: var(--ink); }
Why it matters Writing the dark tokens twice, once for the system preference and once for the explicit choice, lets a manual toggle override the system either way.

Module 38 of 97, phase 04

Stylesheets that scale

CSS architecture: BEM, utilities and layers

As a site grows, naming and structure matter more than any single property. BEM, utility classes and cascade layers are three ways to keep CSS predictable.

In detail

BEM names blocks, elements and modifiers as card, card__title and card--featured, which keeps specificity flat and intent obvious. Utility first CSS such as Tailwind composes small single purpose classes. Layers order resets, base, components and utilities so the cascade stays under control. Most teams mix these approaches.

bem.css
CSS
.card {}.card__title {}.card--featured {}
Why it matters BEM keeps every selector one class deep, so any rule can be overridden by another single class.

Phase 05, modules 39 to 48

The nervous system

JavaScript foundations

How scripts load, the language itself, and asynchronous code that waits without freezing the page.

Module 39 of 97, phase 05

Three ways to wake a page

Inline, internal and external JavaScript

JavaScript can sit in an event attribute, inside a script element, or in an external file loaded with src. How you load external files, with defer, async or type="module", decides when they run.

In detail

Inline handlers such as onclick mix markup and behaviour and are blocked by strict security policies. A plain script in the head blocks parsing. defer downloads in parallel and runs after the HTML is parsed, in order. async runs as soon as it downloads, in any order, which suits independent scripts like analytics. Modules are deferred by default.

Ways to load JavaScript and when each one runs
WayMarkupBlocks HTML parsingRunsUse it for
Inline handleronclick="save()"NoWhen the event firesAvoid, blocked by strict CSP
Internal<script>...</script>YesImmediately, where it sitsTiny snippets such as a theme preload
External<script src="app.js">Yes, while downloading and runningImmediately after downloadRarely, prefer defer
defer<script defer src>NoAfter parsing, in orderMost page scripts
async<script async src>Only while runningAs soon as downloaded, any orderIndependent scripts like analytics
module<script type="module" src>No, deferred by defaultAfter parsing, with importsModern code with import and export
3 examples
inline
HTML
<button onclick="alert('Hi')">Say hi</button>
Why it matters Inline handlers cannot be reused, are hard to test, and are the first thing a strict CSP blocks.

Module 40 of 97, phase 05

Boxes with names

Variables and data types

Variables store values. Use const by default, let when the value changes, and never var. JavaScript has primitive types such as string, number, boolean, null and undefined, and objects for everything else.

In detail

const prevents reassignment, not mutation: a const array can still get new items. typeof reports the type, with the famous quirk that typeof null is object. Template literals with backticks embed values in strings. Numbers are floating point, so 0.1 + 0.2 is not exactly 0.3; use BigInt or integers of cents for money.

types.js
JavaScript
const name = "Ada";let count = 0;const greeting = `Hello, ${name}. You have ${count} messages.`;
Why it matters Template literals read naturally and avoid the + chains that made old string building error prone.

Module 41 of 97, phase 05

Making decisions

Operators and control flow

Comparison, logical and arithmetic operators combine values, and if, else, switch and the ternary operator choose what runs.

In detail

Always compare with === and !==, which do not convert types. && and || short circuit, ?? supplies a default only for null or undefined, and ?. reads deep properties safely. Truthy and falsy values let you write if (list.length) but can surprise you with 0 and empty strings.

control.js
JavaScript
const city = user?.address?.city ?? "Unknown";const label = count > 0 ? `${count} items` : "Empty";
Why it matters ?? only replaces null and undefined, while || also replaces 0 and empty strings. Most default value bugs come from mixing them up.

Module 42 of 97, phase 05

Reusable recipes

Functions, scope and closures

Functions package steps you can reuse. They take parameters, return values, and remember the variables around where they were created, which is called a closure.

In detail

Arrow functions are short and do not have their own this. Default parameters and rest parameters keep signatures clean. Variables declared with let and const live in the nearest block. Closures power counters, private state, event handlers and debounce helpers.

functions.js
JavaScript
const add = (a, b = 0) => a + b;function counter() { let n = 0; return () => ++n; }
Why it matters debounce is a closure in disguise: timer survives between calls because the returned function remembers it.

Module 43 of 97, phase 05

Collections of things

Arrays, objects and JSON

Arrays hold ordered lists and objects hold named properties. map, filter, find and reduce transform arrays, and destructuring and spread copy and combine them.

In detail

Prefer methods that return new arrays over mutating loops; it makes code easier to follow and fits how frameworks detect changes. JSON is text that looks like objects; JSON.parse reads it and JSON.stringify writes it. structuredClone makes a deep copy.

collections.js
JavaScript
const titles = posts.filter((p) => p.published).map((p) => p.title);const { title, tags = [] } = post;
Why it matters Chaining filter, map and reduce describes what you want instead of how to loop, which is easier to read and test.

Module 44 of 97, phase 05

Doing it again and again

Loops and iteration

for, for...of, while and array methods repeat work. for...of iterates values of arrays, strings, Maps and Sets; for...in iterates object keys and is rarely what you want.

In detail

Use forEach or for...of to perform side effects, map to transform, and a classic for loop when you need the index and early exit with break. Object.entries turns an object into pairs you can loop over. Map and Set are better than objects when keys are not strings or must be unique.

loops.js
JavaScript
for (const item of cart) total += item.price;for (const [key, value] of Object.entries(settings)) console.log(key, value);
Why it matters Object.entries plus destructuring in for...of is the cleanest way to walk an object's keys and values together.

Module 45 of 97, phase 05

Blueprints for objects

Classes, objects and this

Classes are blueprints for objects with shared methods. this refers to the object a method was called on, which changes depending on how the function is called.

In detail

A constructor sets up fields, methods live on the prototype, # fields are truly private, and extends with super builds on another class. In event handlers this is the element unless you use an arrow function or bind. Many modern codebases prefer plain functions and objects, but classes remain common in web components and libraries.

cart.js
JavaScript
class Cart {  #items = [];  add(item) { this.#items.push(item); return this; }}
Why it matters #items cannot be read or changed from outside the class, unlike an underscore name that is private only by convention.

Module 46 of 97, phase 05

Code in tidy files

ES modules: import and export

Modules split code across files. export shares values, import brings them in, and the browser loads them with script type="module".

In detail

Named exports suit utilities; a default export suits a file with one main thing. Module scripts run in strict mode, are deferred, and load relative paths with the file extension. Dynamic import() loads code only when needed, which is how bundlers and frameworks split large apps.

main.js
JavaScript
import { formatDate } from "./utils/date.js";const { showChart } = await import("./chart.js");
Why it matters Dynamic import keeps the chart code out of the first download until someone actually asks for it.

Module 47 of 97, phase 05

Waiting without freezing

Async code, promises and async await

JavaScript runs on one thread, so slow work such as network requests is asynchronous. A promise represents a future value, and async and await let you write that waiting code top to bottom.

In detail

The event loop runs your code, then queued callbacks. A promise is pending, then fulfilled or rejected. await pauses only the current async function, not the page. Promise.all runs tasks in parallel and fails fast, Promise.allSettled waits for all, and AbortController cancels requests.

async.js
JavaScript
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
Why it matters Starting independent requests together with Promise.all cuts waiting time to the slowest request instead of the sum of all.

Module 48 of 97, phase 05

When things go wrong

Errors and debugging

try, catch and finally handle errors, throw raises them, and the console, breakpoints and the debugger statement help you find their cause.

In detail

Catch errors where you can do something useful, such as showing a message or retrying. Throw Error objects, not strings, so you get a stack trace. Add a global handler for unhandled promise rejections to report crashes. In DevTools, set breakpoints, step through code and inspect variables instead of scattering console.log everywhere.

errors.js
JavaScript
try { save(data); } catch (err) { showToast(err.message); } finally { spinner.hidden = true; }
Why it matters finally guarantees cleanup such as hiding a spinner even when the request throws.

Phase 06, modules 49 to 56

Talking to the page

DOM, events and browser APIs

Selecting and changing elements, reacting to people, fetching data and remembering things.

Module 49 of 97, phase 06

Finding elements in the tree

The DOM and selecting elements

The DOM is the live tree of objects the browser builds from HTML. querySelector and querySelectorAll find elements with the same selectors you use in CSS.

In detail

document is the root. Every element has parent, children and siblings you can walk. querySelector returns the first match or null; querySelectorAll returns a static list you can loop with forEach. closest() walks up to the nearest matching ancestor, which is the key to event delegation.

document

  • htmldocument.documentElement
  • headtitle, meta, link
  • bodydocument.body
  • header
  • navquerySelector('nav')
  • main
  • article.cardquerySelectorAll('.card')
  • h2card.querySelector('h2')
  • p#text node inside
  • a.card__linkel.closest('.card') walks up
  • footer

Every element is a node with a parent, children and siblings you can walk from JavaScript.

select.js
JavaScript
const title = document.querySelector("h1");const cards = document.querySelectorAll(".card");
Why it matters Always check for null after querySelector. A missing element is the most common cause of Cannot read properties of null.

Module 50 of 97, phase 06

Rewriting the page live

Creating and changing elements

JavaScript changes text, attributes, classes and styles, and creates, moves and removes elements. classList and dataset are the everyday tools.

In detail

Use textContent to set text safely; innerHTML parses HTML and is a security risk with user input. createElement plus append builds nodes, template elements clone ready made markup, and a DocumentFragment batches many insertions into one update.

render-list.js
JavaScript
const li = document.createElement("li");li.textContent = item.name;list.append(li);
Why it matters replaceChildren with a fragment updates the list in one go, so the browser lays out once instead of once per item.

Module 51 of 97, phase 06

Listening to people

Events and event delegation

Events fire when people click, type, scroll or submit. addEventListener attaches a handler, and the event object says what happened and where.

In detail

Events bubble up from the target to its ancestors, so one listener on a parent can handle clicks on many children. That is event delegation, and it works for elements added later. preventDefault stops default actions such as following a link; passive listeners tell the browser a scroll handler will not block scrolling.

events.js
JavaScript
list.addEventListener("click", (e) => {  const btn = e.target.closest("button[data-action]");  if (btn) handle(btn.dataset.action);});
Why it matters Delegation means you never re-attach listeners after rendering new items, which removes a whole class of bugs.

Module 52 of 97, phase 06

Forms that feel smart

Handling forms with JavaScript

JavaScript can intercept submit, read every field with FormData, validate, show inline errors and send data with fetch without reloading the page.

In detail

Listen for submit on the form rather than click on the button so Enter works. new FormData(form) collects named fields, and Object.fromEntries turns it into an object. setCustomValidity plugs your own rules into native validation messages.

signup.js
JavaScript
form.addEventListener("submit", async (e) => {  e.preventDefault();  const data = Object.fromEntries(new FormData(form));});
Why it matters Disabling the button while the request is in flight prevents double submissions from impatient clicks.

Module 53 of 97, phase 06

Getting data from servers

Fetch API and JSON

fetch sends HTTP requests from JavaScript and returns a promise of a response. You check the status, read JSON, and render it.

In detail

fetch only rejects on network failure, so always check response.ok for 4xx and 5xx. Send JSON with a Content-Type header and JSON.stringify. Use AbortController to cancel stale requests, for example when someone types a new search before the old one returns. Cross origin requests follow CORS rules set by the server.

search.js
JavaScript
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);if (!res.ok) throw new Error(res.status);const results = await res.json();
Why it matters Aborting the previous request stops a slow old response from overwriting the results for the newer search.

Module 54 of 97, phase 06

Remembering between visits

localStorage, sessionStorage, cookies and IndexedDB

Browsers can remember data: localStorage persists strings, sessionStorage lasts for a tab, cookies travel with every request, and IndexedDB stores large structured data.

In detail

Use localStorage for small preferences such as a theme, wrapped in try and catch because it can be full or blocked. Never store tokens or secrets there, since any script on the page can read it. Session cookies set by the server with HttpOnly and Secure are safer for authentication.

prefs.js
JavaScript
localStorage.setItem("theme", "dark");const theme = localStorage.getItem("theme") ?? "light";
Why it matters The storage event fires in other tabs, which keeps a theme choice in sync across every open tab.

Module 55 of 97, phase 06

Watching without polling

IntersectionObserver, ResizeObserver and MutationObserver

Observers tell you when something changes without checking in a loop: when an element enters the viewport, when it resizes, or when the DOM changes.

In detail

IntersectionObserver powers lazy loading, infinite scroll, scroll reveal animations and the active link in a table of contents. ResizeObserver reacts to an element's size, which media queries cannot see. MutationObserver watches the DOM for added or changed nodes. All of them are cheaper than scroll and resize handlers.

toc-spy.js
JavaScript
const io = new IntersectionObserver((entries) => { /* ... */ }, { rootMargin: "-40% 0px -55% 0px" });document.querySelectorAll("section[id]").forEach((s) => io.observe(s));
Why it matters The rootMargin shrinks the viewport to a thin band in the middle, so exactly one section counts as current at a time.

Module 56 of 97, phase 06

Doing things on time

Timers and requestAnimationFrame

setTimeout runs code once after a delay, setInterval repeats it, and requestAnimationFrame runs code right before the next screen paint for smooth animation.

In detail

Always keep the id so you can clear timers. Use requestAnimationFrame for anything visual: it matches the display refresh rate and pauses in background tabs. Read layout values such as getBoundingClientRect before writing styles in the same frame to avoid forced reflow.

count-up.js
JavaScript
function frame(now) { /* update */ requestAnimationFrame(frame); }requestAnimationFrame(frame);
Why it matters Basing progress on elapsed time rather than frame count keeps the animation the same length on 60 Hz and 120 Hz screens.

Phase 07, modules 57 to 66

Building blocks

Media, cards and UI components

The pieces every site reuses: cards, navbars, heroes, buttons, dialogs, tabs, carousels, toasts and icons.

Module 57 of 97, phase 07

The most reused box on the web

Media items and card components

A card groups an image or video, a title, short text and an action into one clickable unit. Media items are cards laid out horizontally, with the media beside the text.

In detail

Build cards from semantic parts: article for the card, img with aspect-ratio and object-fit for the media, a heading, and one link stretched over the card with a ::after overlay so the whole card is clickable without nesting links. Put cards in an auto fitting grid and let container queries switch to the media item layout when there is room.

Live previewrendered from the code below
card.html
HTML
<article class="card">  <img src="flex.jpg" alt="" />  <h3><a href="/flexbox">Flexbox in 5 minutes</a></h3>  <p>6 min read</p></article>
Why it matters The stretched ::after link makes the whole card clickable while screen readers still hear one clear link, the title.

Module 59 of 97, phase 07

First impressions

Hero sections

The hero is the first screen: a headline, a supporting sentence, one primary action, and usually an illustration or image.

In detail

Lead with what the site does in plain words. Give the main button the strongest colour and a secondary link a quieter style. Use a two column grid that stacks on phones, clamp() for the headline size, and preload the hero image so it is not your slowest paint.

Live previewrendered from the code below
hero.html
HTML
<section class="hero">  <h1>Ship websites faster</h1>  <p>Everything you need, nothing you do not.</p>  <a class="btn" href="#start">Get started</a></section>
Why it matters fetchpriority="high" tells the browser to fetch the hero image before other images, which usually improves Largest Contentful Paint.

Module 60 of 97, phase 07

Things that ask to be pressed

Buttons, links and chips

Buttons perform actions, links go places, and chips are small pill shaped labels or filters. Getting the right element matters more than the styling.

In detail

Use button for actions and a for navigation, even if they look the same. Give interactive elements a minimum target of about 44 pixels, visible hover, active and focus states, and a disabled style. Chips work well as hashtag style links to topic pages, which also helps search engines connect related content.

Live previewrendered from the code below
buttons.css
CSS
.btn { padding: 10px 18px; border-radius: 10px; }.chip { padding: 4px 10px; border-radius: 999px; }
Why it matters min-height: 44px keeps buttons comfortable to tap on phones without changing how they look on desktop.

Module 62 of 97, phase 07

Show one thing at a time

Tabs and accordions

Accordions stack collapsible sections and tabs switch between panels. details and summary give you an accessible accordion with no JavaScript at all.

In detail

Add a shared name attribute to details elements to make them exclusive, so opening one closes the others. Tabs need role="tablist", role="tab" with aria-selected and aria-controls, and arrow key navigation; keep content in the HTML so it is indexable.

Live previewrendered from the code below
faq.html
HTML
<details name="faq"><summary>What is HTML?</summary><p>The structure of a page.</p></details>
Why it matters Sharing name="faq" across details turns them into an exclusive accordion natively in current browsers.

Module 64 of 97, phase 07

A quiet tap on the shoulder

Toasts and notifications

A toast is a short message that appears briefly in a corner to confirm an action, such as Saved or Copied, then disappears.

In detail

Put toasts in a container with role="status" or aria-live="polite" so screen readers announce them. Do not hide errors that need action in a toast, and pause the timer on hover. The popover attribute is a modern way to layer such UI above everything without z-index battles.

Live previewrendered from the code below
toast.js
JavaScript
function toast(text) {  const el = Object.assign(document.createElement("div"), { className: "toast", textContent: text });  region.append(el); setTimeout(() => el.remove(), 3000);}
Why it matters A polite live region announces the toast without interrupting what the screen reader was saying.

Module 65 of 97, phase 07

Sharp at every size

Icons and SVG

SVG draws icons and illustrations with vectors that stay sharp at any size and can be coloured with CSS. Sprite sheets and icon sets like Iconify make large icon libraries easy.

In detail

Inline SVG with fill="currentColor" inherits text colour, so icons follow themes automatically. A sprite with symbol and use avoids repeating paths. Decorative icons get aria-hidden="true"; meaningful icons need a label. Icon fonts are outdated: they blur, fail to load and confuse screen readers.

Live previewrendered from the code below
icons.html
HTML
<svg class="icon" aria-hidden="true"><use href="#i-check" /></svg>
Why it matters currentColor means one icon file works in light mode, dark mode and every button colour.

Module 66 of 97, phase 07

Fast pictures

Image and media performance

Images are usually the heaviest part of a page. Right sizing, modern formats, lazy loading and reserved space make them fast and stable.

In detail

Export at the largest size actually displayed, compress, and prefer AVIF or WebP. Set width and height or aspect-ratio to avoid layout shift. Lazy load below the fold, but never the hero. Use a CDN that resizes on the fly when you have many images.

images.html
HTML
<img src="photo-800.avif" width="800" height="600" alt="..." loading="lazy" decoding="async" />
Why it matters width and height let the browser reserve the right space before the image arrives, which prevents layout shift.

Phase 08, modules 67 to 71

One complete page

Assembling a single page website

Putting every block together into one page with scroll reveals, smooth scrolling, parallax, themes and shortcuts.

Module 67 of 97, phase 08

Every block in its place

Assembling a complete single page

A single page website stacks sections on one page: header and navigation, hero, features, cards, testimonials, FAQ and footer, with in page links between them.

In detail

Start with semantic HTML for every section, then a design token layer, layout with grid and flexbox, and components. Add JavaScript last for the hamburger, theme toggle and reveals, so the page still works if a script fails. Give each section an id for deep links and scroll-margin-top so headings are not hidden under a sticky header.

  1. 01StructureSemantic HTML for every section, readable with CSS off.
  2. 02TokensColours, type and spacing as custom properties, light and dark.
  3. 03LayoutGrid for the page, flexbox inside components.
  4. 04ComponentsNavbar, hero, cards, chips, tabs, dialog, footer.
  5. 05BehaviourHamburger, theme toggle, shortcuts, reveals.
  6. 06PolishPerformance, accessibility and SEO checks.
index.html
HTML
Why it matters width: min(1120px, 100% - 32px) is a one line container that is fluid on phones and capped on desktops.

Module 68 of 97, phase 08

Content that arrives on cue

Scroll reveal animations

Sections fade and slide in as they enter the viewport. IntersectionObserver adds a class once, and CSS does the animation.

In detail

Start content visible and only hide it when JavaScript is running, so nothing stays invisible if a script fails. Animate opacity and transform only, reveal each element once, and stagger children with a CSS variable for delay. Respect prefers-reduced-motion by skipping the effect.

reveal.js
JavaScript
const io = new IntersectionObserver((es) => es.forEach((e) => e.isIntersecting && e.target.classList.add("in")));document.querySelectorAll(".reveal").forEach((el) => io.observe(el));
Why it matters Hiding only under a .js class means search engines and people without JavaScript always see the content.

Module 69 of 97, phase 08

Smooth scroll and depth

Lenis smooth scrolling and parallax

Lenis smooths wheel and trackpad scrolling while keeping native scroll behaviour. Parallax moves layers at different speeds as you scroll to suggest depth.

In detail

Create one Lenis instance, drive it from requestAnimationFrame, and use lenis.scrollTo for in page links. For parallax, read each layer's position on scroll and translate it by a fraction of its distance from the centre of the screen. Turn both off for prefers-reduced-motion, and mark inner scroll areas with data-lenis-prevent.

smooth.js
JavaScript
const lenis = new Lenis({ lerp: 0.1 });function raf(t) { lenis.raf(t); requestAnimationFrame(raf); }requestAnimationFrame(raf);
Why it matters Measuring each layer against the centre of the viewport keeps parallax offsets small and symmetrical, so nothing drifts off screen.

Module 70 of 97, phase 08

Light or dark, your call

A theme toggle that remembers

A theme toggle sets data-theme on the html element, saves the choice, and restores it before the page paints so there is no flash of the wrong theme.

In detail

Read the saved choice in a tiny inline script in the head, before the stylesheet applies, then fall back to the system preference. Update aria-pressed on the button and the theme-color meta tag so the mobile browser bar matches.

theme.js
HTML
const next = root.dataset.theme === "dark" ? "light" : "dark";root.dataset.theme = next; localStorage.setItem("theme", next);
Why it matters The inline head script runs before first paint, which is the only way to avoid a white flash for people who chose dark.

Module 71 of 97, phase 08

Power at your fingertips

Keyboard shortcuts and full screen

Single key shortcuts such as T for theme, F for full screen, M for menu and slash for search make a long page fast to use, as long as they never fire while typing.

In detail

Listen for keydown on document, ignore events with modifier keys and events from inputs, textareas and contenteditable. Document the shortcuts on the page. The Fullscreen API toggles full screen with requestFullscreen and exitFullscreen, and fires fullscreenchange so your button can stay in sync.

shortcuts.js
JavaScript
document.addEventListener("keydown", (e) => {  if (e.target.closest("input, textarea, [contenteditable]")) return;  if (e.key === "t") toggleTheme();});
Why it matters Skipping keys typed into fields is what keeps single letter shortcuts from hijacking a search box.

Phase 09, modules 72 to 77

Styling at scale

Sass, Tailwind, Bootstrap and CSS in JS

Why CSS libraries exist, what each one trades, and how to pick.

Module 72 of 97, phase 09

CSS with superpowers, compiled

Sass and preprocessors

Sass adds variables, nesting, mixins, functions and partials to CSS, then compiles to plain CSS. It was the first big answer to large stylesheets.

In detail

Many Sass features now exist natively: custom properties, nesting and color-mix. Sass still shines for loops that generate utility classes, mixins for repeated patterns, and splitting styles into partials joined with @use. Vite and most frameworks compile .scss files automatically once the sass package is installed.

styles.scss
CSS
$brand: #1d63ed;@mixin card { border-radius: 14px; padding: 16px; }.post { @include card; &:hover { border-color: $brand; } }
Why it matters The @each loop writes four utility classes from one rule, something plain CSS still cannot do.

Module 73 of 97, phase 09

Style without leaving the markup

Tailwind CSS (utility first)

Tailwind gives you small single purpose classes such as flex, p-4 and text-blue-600 that you combine in HTML. A build step keeps only the classes you use.

In detail

Utility first trades long class lists for speed and consistency: spacing, colours and breakpoints come from one theme, and there is no naming or dead CSS. Prefixes handle states and breakpoints: hover:, md:, dark:. Extract repeated patterns into components in your framework rather than into custom CSS classes.

card-tailwind.html
HTML
<article class="rounded-2xl border p-4 hover:scale-[1.02] transition">...</article>
Why it matters Every hover:, dark: and lg: variant here is generated only because it appears in your files, so the shipped CSS stays tiny.

Module 74 of 97, phase 09

Ready made parts

Bootstrap and component frameworks

Bootstrap ships a grid, ready styled components such as navbars, cards and modals, and a JavaScript bundle to make them work. It is the fastest way to a consistent, conventional site.

In detail

Use the 12 column grid with container, row and col classes, add components by copying their markup, and customise colours through Sass variables or CSS variables. The trade off is that Bootstrap sites look alike unless you theme them, and you ship styles for components you may not use.

bootstrap.html
HTML
<div class="container"><div class="row"><div class="col-md-4"><div class="card">...</div></div></div></div>
Why it matters stretched-link is Bootstrap's version of the ::after overlay trick that makes the whole card clickable.

Module 75 of 97, phase 09

Styles written in JavaScript

CSS in JS (JSS, styled-components, Emotion)

CSS in JS writes styles in JavaScript files next to components, generates unique class names, and can change styles from props. JSS, styled-components and Emotion are the best known.

In detail

It solved global naming clashes and made dynamic styles easy in React. The cost is runtime work in the browser and friction with server components. Newer zero runtime tools such as vanilla-extract and Panda extract static CSS at build time, and many teams now prefer CSS Modules or Tailwind instead.

Button.jsx
JavaScript
const Button = styled.button`  background: ${(p) => (p.$primary ? "#1d63ed" : "white")};`;
Why it matters The $primary prop is a transient prop: styled-components uses it for styling but does not pass it to the DOM.

Module 76 of 97, phase 09

Local by default

CSS Modules

CSS Modules are ordinary CSS files whose class names are made unique at build time, so .title in one component never clashes with .title in another.

In detail

Name the file Card.module.css, import it as an object, and use styles.title as the class. You keep plain CSS, zero runtime cost and full support for media queries and variables. Vite and Next.js support CSS Modules without configuration.

Card.module.css
CSS
.card { border-radius: 14px; }/* import styles from "./Card.module.css"; <article className={styles.card}> */
Why it matters composes reuses another class's rules inside the module, which keeps variants DRY without global names.

Module 77 of 97, phase 09

Do not build every button yourself

Component libraries and design systems

Component libraries such as MUI, Chakra, Radix and shadcn/ui ship accessible, themeable components for frameworks. A design system is your own documented set of tokens and components.

In detail

Headless libraries like Radix give behaviour and accessibility with no styles, so you bring Tailwind or CSS. Styled libraries like MUI give a complete look. shadcn/ui copies component source into your project so you own it. Whatever you pick, define tokens first so components stay consistent.

Ways to style a site, compared
ApproachYou writeShipsStrengthWatch out for
Plain CSSSelectors and propertiesOnly your CSSNo build, full controlNaming collisions at scale
SassSCSS with nesting and mixinsCompiled CSSOrganisation, reuseNeeds a build step
TailwindUtility classes in HTMLOnly used utilitiesSpeed, consistencyLong class lists
BootstrapComponent classesFramework CSS and JSFast prototypesSites look alike
CSS ModulesScoped .module.css filesHashed class namesNo collisionsFramework tooling needed
CSS in JSStyles inside componentsRuntime or extracted CSSDynamic stylesRuntime cost
Component librariesImported componentsPre built UIAccessible behaviourCustomising deeply

Phase 10, modules 78 to 85

Why frameworks

React and Next.js

The problem plain DOM code runs into, how React solves it, and why Next.js wraps React for real websites.

Module 78 of 97, phase 10

When the page outgrows by hand

Why React and Next.js came up

Plain JavaScript works until the interface has lots of state: you end up writing code that keeps the DOM in sync by hand. Frameworks let you describe what the UI should look like for a given state and update the DOM for you.

In detail

jQuery made DOM work easier but still manual. React, released by Facebook in 2013, introduced components and a declarative model: UI is a function of state. Single page apps rendered in the browser were slow to first paint and hard for search engines, so Next.js added server rendering, static generation, routing and image optimisation on top of React.

  1. 1991HTMLTim Berners-Lee publishes the first web pages.
  2. 1995JavaScriptBrendan Eich writes it in ten days at Netscape.
  3. 1996CSSStyling moves out of the markup.
  4. 2006jQueryWrite less, do more: one API over messy browsers.
  5. 2010AngularJSTwo way binding and single page apps go mainstream.
  6. 2013ReactComponents and UI as a function of state.
  7. 2014VueA gentler, progressive framework.
  8. 2016Next.jsReact with routing and server rendering built in.
  9. 2017TailwindUtility first CSS takes off.
  10. 2023Server componentsRendering moves back toward the server.
vanilla vs react
JavaScript
// vanilla: you update the DOM yourselfcount++; label.textContent = count; button.disabled = count >= 10;// react: you describe the result<button disabled={count >= 10}>{count}</button>
Why it matters In React the UI is recalculated from state every time, so a new rule is one expression instead of another manual DOM update.

Module 79 of 97, phase 10

Lego for interfaces

React components, JSX and props

A React component is a function that returns JSX, HTML like syntax inside JavaScript. Props pass data into components, and components compose into bigger ones.

In detail

JSX uses className instead of class and htmlFor instead of for, and wraps expressions in braces. Components start with a capital letter. Keep components small and pure: same props, same output. children lets a component wrap other markup, which is how layouts and cards are built.

Card.jsx
JavaScript
export function Card({ title, minutes, children }) {  return <article className="card"><h3>{title}</h3><p>{minutes} min</p>{children}</article>;}
Why it matters children turns Card into a frame that any content can fill, which is the core of composition in React.

Module 80 of 97, phase 10

Memory and side effects

State and effects with hooks

useState gives a component memory that triggers a re-render when it changes. useEffect runs side effects such as fetching data or subscribing to events after render.

In detail

Never mutate state; set a new value. Derive values during render instead of storing duplicates. Effects need a dependency array and a cleanup function for subscriptions and timers. Data fetching in modern apps usually moves to the framework, such as Next.js server components or a library like TanStack Query.

ThemeToggle.jsx
JavaScript
const [theme, setTheme] = useState("light");useEffect(() => { document.documentElement.dataset.theme = theme; }, [theme]);
Why it matters Passing a function to setTheme uses the latest value even when several updates happen quickly.

Module 81 of 97, phase 10

Many items, many inputs

Lists, keys and forms in React

Render lists with map and give each item a stable key. Controlled inputs keep form values in state so React is the single source of truth.

In detail

Keys must be unique among siblings and stable, such as an id; using the array index breaks when items are reordered. Filter and sort during render. React 19 form actions let a form call a function directly with FormData, which simplifies submissions.

PostList.jsx
JavaScript
{posts.filter((p) => p.title.includes(q)).map((p) => <li key={p.id}>{p.title}</li>)}
Why it matters visible is calculated during render instead of stored in state, so it can never fall out of sync with the query.

Module 82 of 97, phase 10

React, ready for the real web

Why Next.js

Next.js is a React framework that adds file based routing, server rendering, static generation, image and font optimisation, and API routes, so a React app becomes a fast, indexable website.

In detail

Plain React renders in the browser, so the first HTML is empty until JavaScript loads, which hurts speed and SEO. Next.js renders on the server or at build time and sends real HTML, then React takes over for interactivity. Server Components keep data fetching and heavy code on the server and send less JavaScript.

terminal
Shell
npx create-next-app@latest my-blogcd my-blog && npm run dev
Why it matters The build output lists which routes are static and which are dynamic, which is the quickest way to understand how your site is served.

Module 83 of 97, phase 10

Folders become pages

Next.js App Router, layouts and metadata

In the App Router, folders in app define routes, page.jsx renders a page, layout.jsx wraps pages with shared UI, and an exported metadata object writes the SEO tags.

In detail

Components are server components by default and can be async to fetch data directly. Add "use client" at the top of a file for state, effects and event handlers. Dynamic segments like [slug] read params, generateStaticParams prerenders them, and next/image and next/link optimise images and navigation.

app/blog/[slug]/page.jsx
JavaScript
export default async function Post({ params }) {  const post = await getPost((await params).slug);  return <article><h1>{post.title}</h1></article>;}
Why it matters generateStaticParams turns every post into static HTML at build time, so pages are as fast as plain files.

Module 84 of 97, phase 10

Where and when HTML is made

CSR, SSR, SSG and ISR

Rendering strategy decides where HTML is built: in the browser (CSR), on the server per request (SSR), once at build time (SSG), or at build time with periodic refresh (ISR).

In detail

Static is fastest and cheapest, perfect for blogs and docs. Server rendering suits personalised or fast changing pages. Client rendering suits dashboards behind a login. Modern frameworks mix them per route, and streaming sends parts of a page as soon as they are ready.

Where and when the HTML is built
StrategyHTML builtFirst loadFreshnessGood for
CSR, client sideIn the browser by JavaScriptSlower, blank until JS runsAlways liveDashboards behind a login
SSR, server sideOn the server per requestFast, full HTMLAlways livePersonalised or fast changing pages
SSG, staticAt build timeFastest, from a CDNUntil the next buildBlogs, docs, marketing
ISR, incrementalAt build, then regeneratedFast, from a CDNRefreshed on a timer or on demandLarge catalogs, news
Streaming and RSCOn the server in chunksFast, shell firstLiveMixed pages in Next.js app router

Module 85 of 97, phase 10

Other ways to build

Vue, Svelte, Angular and Astro

React is not the only option. Vue offers approachable templates, Svelte compiles components away, Angular is a full platform, and Astro ships zero JavaScript by default with islands of interactivity.

In detail

Each has a meta framework: Nuxt for Vue, SvelteKit for Svelte, Analog for Angular. Astro suits content sites and blogs because pages are static HTML unless a component opts into JavaScript. The core ideas, components, state, props and routing, transfer between all of them.

Counter.vue
HTML
<script setup>import { ref } from "vue"; const count = ref(0);</script><template><button @click="count++">{{ count }}</button></template>
Why it matters client:visible hydrates the counter only when it scrolls into view, so the rest of the page ships no JavaScript.

Phase 11, modules 86 to 94

Ship it

Tooling, performance, security and Docker

Packages, bundlers, types, quality checks, version control, speed, safety and shipping in a container.

Module 86 of 97, phase 11

Borrowing other people's code

npm, packages and package.json

npm installs open source packages and records them in package.json. Scripts in package.json run common tasks such as dev, build and test.

In detail

dependencies ship with the app, devDependencies are only for building. The lockfile pins exact versions so every machine installs the same thing; commit it. Semantic versions read major.minor.patch, and a caret allows minor and patch updates. Check a package's size and maintenance before adding it.

package.json
JSON
{ "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "lenis": "^1.3.26" } }
Why it matters "type": "module" makes .js files ES modules in Node, matching how the browser treats your code.

Module 87 of 97, phase 11

From many files to a fast bundle

Vite and bundlers

A bundler takes your modules, CSS and assets and produces optimised files for production. Vite serves files instantly in development and bundles with Rollup for production.

In detail

Vite gives a dev server with hot module replacement, supports TypeScript, JSX, CSS Modules, Sass and PostCSS out of the box, and hashes file names so browsers can cache them forever. Webpack, Parcel, esbuild and Turbopack solve the same problem with different trade offs.

terminal
Shell
npm create vite@latest my-site -- --template vanillacd my-site && npm install && npm run dev
Why it matters Hashed file names change whenever content changes, so you can cache assets for a year without serving stale code.

Module 88 of 97, phase 11

Catch mistakes before the browser does

TypeScript for the web

TypeScript adds types to JavaScript so editors catch mistakes as you type: a missing property, a wrong argument, a possibly null element.

In detail

Types are erased at build time, so the browser still runs JavaScript. Start with type inference, add types to function parameters and shared data, and turn on strict mode early. querySelector returns Element | null, which forces you to handle a missing element instead of crashing.

cart.ts
TypeScript
type Item = { id: string; price: number };const total = (items: Item[]): number => items.reduce((s, i) => s + i.price, 0);
Why it matters A union of string literals such as "light" | "dark" turns typos into compile errors.

Module 89 of 97, phase 11

Consistent and careful

Linting and formatting: ESLint and Prettier

Prettier formats code the same way for everyone, and ESLint flags likely bugs such as unused variables or missing awaits. Together they end style debates and catch mistakes early.

In detail

Run Prettier on save in your editor, ESLint in the editor and in CI, and a pre commit hook so bad code never lands. Stylelint does the same for CSS, and axe or eslint-plugin-jsx-a11y catch accessibility mistakes.

eslint.config.js
JavaScript
import js from "@eslint/js";export default [js.configs.recommended];
Why it matters eqeqeq turns every == into an error, which removes a whole family of type coercion bugs.

Module 90 of 97, phase 11

A time machine for code

Git and GitHub

Git records every change to your project so you can go back, branch off and merge. GitHub hosts repositories, reviews pull requests and runs automation.

In detail

Commit small, focused changes with clear messages. Work on a branch, open a pull request, and merge after review. A .gitignore keeps node_modules, builds and secrets out. GitHub Actions can lint, test, build and deploy on every push.

terminal
Shell
git init && git add . && git commit -m "Initial page"git switch -c feature/navbar
Why it matters git add -p shows each change before staging it, which keeps unrelated edits out of a commit.

Module 91 of 97, phase 11

Fast is a feature

Web performance and Core Web Vitals

Core Web Vitals measure real user experience: Largest Contentful Paint for loading, Interaction to Next Paint for responsiveness, and Cumulative Layout Shift for visual stability.

In detail

Aim for LCP under 2.5 seconds, INP under 200 milliseconds and CLS under 0.1. Ship less JavaScript, compress and cache assets, preload the hero image and fonts, reserve space for media, and break long tasks. Measure with Lighthouse in the lab and with real user data in the field.

vitals.js
JavaScript
import { onLCP, onINP, onCLS } from "web-vitals";onLCP(console.log); onINP(console.log); onCLS(console.log);
Why it matters Field data from real visitors catches slow phones and networks that a fast laptop running Lighthouse never sees.

Module 92 of 97, phase 11

Locks on every window

Web security basics

The most common front end risks are cross site scripting (XSS), unsafe third party scripts, leaking secrets, and mixed content. HTTPS, careful DOM handling and a Content Security Policy prevent most of them.

In detail

Never put untrusted input into innerHTML; use textContent or a sanitiser. Keep API keys on the server, because anything in front end code is public. A CSP header limits where scripts can load from, Subresource Integrity checks CDN files, and cookies for sessions should be HttpOnly, Secure and SameSite.

safe-render.js
JavaScript
el.textContent = userInput;          // safe// el.innerHTML = userInput;         // XSS risk
Why it matters textContent can never execute anything, which makes it the default choice for showing data you did not write.

Module 93 of 97, phase 11

Ship it in a box

Containerising a website with Docker

Docker packages your site and its web server into an image that runs the same on any machine. A multi stage build compiles with Node, then serves the static files with a tiny Nginx image.

In detail

The first stage installs dependencies and runs the build; the second copies only dist into nginx:alpine, so the final image is a few megabytes with no Node or source code. Add a .dockerignore, an Nginx config for caching and single page app fallbacks, and run it locally with docker compose before deploying.

3 examples
Dockerfile
Dockerfile
FROM node:22-alpine AS buildRUN npm ci && npm run buildFROM nginx:alpineCOPY --from=build /app/dist /usr/share/nginx/html
Why it matters Copying the lockfile before the source means the npm ci layer is cached until dependencies actually change.

Module 94 of 97, phase 11

Putting it on the internet

Hosting, domains and CDNs

Static sites can be hosted on GitHub Pages, Netlify, Vercel or Cloudflare Pages for free; containers run on platforms that accept Docker images. A CDN copies files to servers near your visitors.

In detail

Connect a repository and the host builds and deploys on every push, with preview links for pull requests. Point your domain's DNS at the host and HTTPS certificates are issued automatically. Set cache headers so hashed assets are cached for a year and HTML is revalidated.

deploy.yml
YAML
on: pushjobs: { deploy: { runs-on: ubuntu-latest, steps: [ ... ] } }
Why it matters Every push to main rebuilds and republishes the site, so deploying is just merging a pull request.

Phase 12, modules 95 to 97

Mastery

Progressive apps, web APIs and the capstone

Installable apps, powerful browser APIs, and a final project that uses every phase.

Module 95 of 97, phase 12

A website you can install

Progressive web apps and service workers

A progressive web app adds a manifest and a service worker so a site can be installed, open in its own window, and work offline.

In detail

The manifest names the app, sets icons and theme colours, and chooses a display mode. The service worker is a script that sits between the page and the network, caching files so repeat visits are instant and offline visits still work. Start with caching the app shell and a fallback page.

sw.js
JavaScript
self.addEventListener("fetch", (e) => e.respondWith(caches.match(e.request).then((r) => r ?? fetch(e.request))));
Why it matters Deleting old caches on activate stops a new deploy from serving yesterday's files forever.

Module 96 of 97, phase 12

The browser is a platform

Powerful web APIs

Browsers expose rich APIs: Canvas for drawing, Clipboard, Web Share, Geolocation, Notifications, Web Audio, View Transitions and many more.

In detail

Feature detect before using any API and ask for permissions only when the person triggers the feature. The View Transitions API animates between page states with one function call. Web Components let you define your own reusable HTML elements without a framework.

apis.js
JavaScript
await navigator.clipboard.writeText(location.href);document.startViewTransition(() => updateTheDom());
Why it matters Custom elements work in any framework or none, which makes them good for small widgets shared across sites.

Module 97 of 97, phase 12

Put every piece on the page

Capstone: a complete single page blog

Build a single page blog that uses every phase: semantic HTML, a token based theme with dark mode, responsive grid and cards, a hamburger navbar, scroll reveals, Lenis smooth scrolling, keyboard shortcuts, then rebuild one section in React and ship the whole thing in Docker.

Questions people ask

Short answers to the questions that come up most while learning to build websites, each linked to the module that covers it in depth.

Do I need to learn HTML and CSS before JavaScript?

Yes. JavaScript changes HTML and CSS, so without them it has nothing to work on. A few weeks of HTML and CSS makes every JavaScript lesson easier.

Module 07: Anatomy of an HTML document
Should I put CSS and JavaScript inline, in the page, or in files?

Use external files for almost everything so the browser can cache them. Keep internal code for tiny, critical snippets and avoid inline handlers.

Module 20: Inline, internal and external CSS
Flexbox or Grid?

Grid for two dimensional page layout, flexbox for lining things up in one direction inside components. Most pages use both.

Module 32: CSS Grid
What does semantic HTML actually change?

Screen readers can jump between landmarks, search engines understand structure, and your CSS selectors become clearer.

Module 17: Semantic layout elements
Why did React come up if JavaScript can already change the page?

Keeping many parts of the screen in sync with data by hand gets error prone. React lets you describe the UI for each state and updates the DOM for you.

Module 78: Why React and Next.js came up
Do I need Next.js for every React project?

No. A dashboard behind a login can be plain React with Vite. Next.js pays off when you need routing, SEO friendly server rendering or static generation.

Module 82: Why Next.js
Tailwind or Bootstrap?

Bootstrap gives ready made components quickly. Tailwind gives building blocks for a custom design. Learn plain CSS first so either one makes sense.

Module 73: Tailwind CSS (utility first)
How do I make a site work in dark mode?

Store colours as custom properties, redefine them under prefers-color-scheme: dark and a data-theme attribute, and set color-scheme so form controls follow.

Module 37: Dark mode and theming
Is smooth scrolling and parallax bad for accessibility?

It can be. Respect prefers-reduced-motion, keep motion subtle, and never hide content that only appears through animation.

Module 69: Lenis smooth scrolling and parallax
What should a beginner use to edit and preview pages?

VS Code with a live server extension, a modern browser, and DevTools open on the side.

Module 06: Editor and project setup
Why does my page look different on phones?

Missing a viewport meta tag, fixed widths, or no media queries. Start mobile first and let content flow.

Module 30: Responsive design and media queries
Why would a website need Docker?

A container packs your built site and its web server so it runs the same on your laptop, in CI and in production.

Module 93: Containerising a website with Docker
How is progress saved on this page?

Progress is saved in this browser only, in local storage. Nothing is sent anywhere.

Module 54: localStorage, sessionStorage, cookies and IndexedDB
What keyboard shortcuts does this page support?

T switches theme, F toggles full screen, M opens the menu, slash searches, J and K move between modules, and Esc closes or exits focus.

Module 71: Keyboard shortcuts and full screen

Official documentation and sources

Every source linked from the modules above, grouped by the phase that uses it and then by where it lives. 290 links in total, all opening in a new tab.

Browsers, servers and the trip in between

Phase 01, How the web works

19

HTML foundations

Phase 02, The skeleton

39

CSS foundations

Phase 03, The skin

32

Modern CSS layout and animation

Phase 04, Layout and motion

23

Library and tool docs1

JavaScript foundations

Phase 05, The nervous system

32

DOM, events and browser APIs

Phase 06, Talking to the page

24

Media, cards and UI components

Phase 07, Building blocks

27

Sass, Tailwind, Bootstrap and CSS in JS

Phase 09, Styling at scale

20

React and Next.js

Phase 10, Why frameworks

23

Tooling, performance, security and Docker

Phase 11, Ship it

30

Credits

The technologies this roadmap teaches and the tools used to build the page. The people behind it are listed in the footer.