Web Dev — Complete Beginner's Reference

Web Development, Exhaustively Explained

A complete, single-page reference for absolute beginners. Plain language, hundreds of working examples, and a glossary of every acronym and piece of jargon you will meet in your first year. Use the sidebar to jump to any subject.

How to use this Don't read it front to back. Skim the foundations, then jump to whatever you're building. Every section is self-contained. Come back to it as a lookup, not a novel.

How the Web Works

The web is a conversation between two computers. Your browser is the client. The machine holding the files is the server. Everything else is detail.

The request/response cycle

  1. You type a URL like example.com/about.
  2. DNS translates that human name into an IP address — the server's phone number.
  3. Your browser opens a TCP connection to that address (encrypted with TLS if the URL is HTTPS).
  4. The browser sends an HTTP request: "please give me /about", plus headers describing what it accepts.
  5. The server sends back an HTTP response: a status code, headers, and a body.
  6. The browser reads the HTML, requests the CSS and JavaScript it references, and paints the page.

The layers, from bottom to top

LayerWhat it does
Physical / LinkCables, Wi‑Fi, Ethernet. Moves raw bits between machines.
IPAddressing and routing. Gets a packet from one machine to another across networks.
TCP / UDPTCP guarantees ordered, reliable delivery; UDP is faster but lossy (used for video, games).
TLSEncrypts the connection. This is what the S in HTTPS means.
HTTPThe request/response protocol browsers and servers speak.
HTML / CSS / JSWhat actually gets sent over HTTP and interpreted by the browser.

Key idea Anything that runs in the browser is visible to the user. Never put passwords, API keys, or secrets in front-end code. That's what the back end is for.

The Three Core Languages

Every website you have ever visited is built from three languages working together.

LanguageJobAnalogyFile extension
HTMLContent and structureThe skeleton.html
CSSAppearance and layoutThe skin and clothing.css
JavaScriptBehaviour and interactivityThe muscles and nerves.js

You can build a perfectly good website with HTML and CSS alone. Add JavaScript only when something needs to respond to the user.

They connect like this

<!-- HTML: structure -->
<link rel="stylesheet" href="styles.css">   <!-- pulls in CSS -->
<script src="app.js"></script>              <!-- pulls in JS   -->

Setting Up Your Environment

You need three things to start: an editor, a browser, and a terminal.

1. Code editor — VS Code

Free, made by Microsoft, and the industry default for beginners. Install it, then add these extensions:

  • Live Server — right-click an HTML file, open it in a browser, and it auto-reloads on save.
  • Prettier — formats your code consistently on save.
  • ESLint — flags JavaScript mistakes as you type.
  • Path Intellisense — autocompletes file paths.

2. Browser — Chrome, Firefox, or Edge

All three have excellent built-in DevTools. Press F12 (or right-click → Inspect) to open them. You will live in here.

3. Terminal

A text interface to your computer. On macOS and Linux it's Terminal; on Windows it's PowerShell or Windows Terminal. The four commands you need first:

pwd            # print working directory — where am I?
ls             # list files (dir on Windows)
cd my-folder   # change directory
mkdir new-dir  # make a new directory

Folder structure for a simple site

my-site/
├── index.html        ← the homepage (this name matters)
├── about.html
├── css/
│   └── styles.css
├── js/
│   └── app.js
└── images/
    └── logo.png

Why index.html? Web servers look for a file named index.html by default when someone requests a directory. So example.com/ serves example.com/index.html automatically.

URLs, HTTP & Status Codes

Anatomy of a URL

https://api.example.com:443/users/42?sort=name&limit=10#bio
└─┬─┘   └──────┬──────┘└┬─┘└───┬───┘└────────┬────────┘└─┬─┘
scheme      host      port   path        query string   fragment

scheme   → protocol (http, https, ftp, mailto)
host     → the server's name
port     → which door on that server (443 = HTTPS, 80 = HTTP)
path     → which resource
query    → extra parameters, after the ?
fragment → jump to a spot inside the page, after the #

Common HTTP methods

MethodMeaningHas a body?
GETRead something. Should never change data.No
POSTCreate something.Yes
PUTReplace something entirely.Yes
PATCHUpdate part of something.Yes
DELETERemove something.Usually no
HEADLike GET but returns only headers.No
OPTIONSAsks what methods are allowed (used by CORS).No

Status codes

RangeCategoryCommon examples
1xxInformational101 Switching Protocols (WebSockets)
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirect301 Moved Permanently, 304 Not Modified (cached)
4xxClient error400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests
5xxServer error500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable

Common request and response headers

HeaderDirectionMeaning
Content-TypeBothWhat kind of data this is: text/html, application/json, image/png
AcceptRequestWhat formats the client can handle
AuthorizationRequestCredentials, usually Bearer <token>
CookieRequestStored cookies for this domain
Set-CookieResponseTells the browser to store a cookie
Cache-ControlResponseHow long the browser may reuse this response
Access-Control-Allow-OriginResponseWhich origins may read this response (CORS)

Part II — HTML

HTML Basics

HTML is a markup language: you wrap content in tags to say what it is. Tags usually come in pairs — an opening tag and a closing tag — and together with their content they form an element.

<p>This is a paragraph.</p>
 │   │                  │   │
 │   │                  │   └─ closing tag
 │   └─ content ────────┘
 └─ opening tag

<!-- This whole line is a comment. Browsers ignore it. -->

Void (self-closing) elements

Some elements have no content and no closing tag:

<img src="cat.jpg" alt="A cat">
<br>
<hr>
<input type="text">
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">

Nesting

Elements go inside other elements. Indentation is for humans — the browser doesn't care — but badly nested tags cause confusing bugs. Close them in reverse order of opening.

<!-- Correct: last opened, first closed -->
<p>Some <strong>bold</strong> text.</p>

<!-- Wrong: overlapping tags -->
<p>Some <strong>bold</p></strong>

Document Structure

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="A short summary for search engines.">
    <title>My First Page</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" href="favicon.ico">
  </head>
  <body>
    <h1>Hello, world!</h1>
    <script src="app.js"></script>
  </body>
</html>
PartPurpose
<!DOCTYPE html>Tells the browser to use modern standards mode. Always first line.
<html lang="en">The root element. lang helps screen readers and translation tools.
<head>Metadata — nothing here is displayed on the page.
<meta charset="UTF-8">Character encoding. Without it, accented characters and emoji break.
<meta name="viewport">Makes the page respect mobile screen width. Essential.
<title>Text in the browser tab and in search results.
<body>Everything the user sees.

Where to put <script> Put scripts at the end of <body>, or add the defer attribute. Otherwise the script runs before the HTML it wants to manipulate exists.

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

Text & Inline Elements

Headings

<h1>Page title — use once per page</h1>
<h2>Major section</h2>
<h3>Subsection</h3>
<h4>Sub-subsection</h4>
<h5>Rarely used</h5>
<h6>Rarely used</h6>

Don't skip levels to get a specific font size — that's what CSS is for.

Paragraphs and breaks

<p>A paragraph of text.</p>
<p>Another paragraph.<br>A line break inside it.</p>
<hr>   <!-- horizontal rule: a thematic break -->

Inline semantics

TagMeaningLooks like
<strong>Strong importancebold
<em>Stress emphasisitalic
<b>Bold, no extra meaningbold
<i>Italic, no extra meaningitalic
<u>Underline (looks like a link — avoid)underlined
<s>No longer accuratestruck
<mark>Highlighted / relevantmarked
<small>Fine printsmall
<code>Inline codecode
<kbd>Keyboard inputCtrl
<abbr>AbbreviationHTML
<sub> / <sup>Subscript / superscriptH2O, x2
<time>A machine-readable date<time datetime="2026-10-12">

Block vs inline

Block elements start on a new line and take full width (<p>, <div>, <h1>). Inline elements flow within text and only take the width they need (<span>, <a>, <strong>).

Escaping special characters

CharacterEntityWhen you need it
&lt;&lt;To show a literal <
&gt;&gt;To show a literal >
&amp;&amp;To show a literal &
&quot;&quot;Double quote inside an attribute
&nbsp;&nbsp;A non-breaking space
&copy;&copy;©

Anchor links

<!-- Absolute URL: points to another site -->
<a href="https://example.com">Visit example.com</a>

<!-- Relative URL: points to a file in your project -->
<a href="about.html">About us</a>
<a href="pages/contact.html">Contact</a>
<a href="/">Home</a>          <!-- leading / = site root -->

<!-- Open in a new tab (add rel for security) -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
  New tab
</a>

<!-- Jump to a spot on the same page -->
<a href="#section-2">Go to section 2</a>
<h2 id="section-2">Section 2</h2>

<!-- Email and phone -->
<a href="mailto:hi@example.com">Email us</a>
<a href="tel:+15551234">Call us</a>

<!-- Download -->
<a href="report.pdf" download>Download the report</a>

Images

<img src="cat.jpg" alt="A tabby cat asleep on a keyboard" width="600" height="400">

<!-- Responsive: let the browser pick the best file -->
<img src="cat-small.jpg"
     srcset="cat-small.jpg 480w, cat-medium.jpg 960w, cat-large.jpg 1440w"
     sizes="(max-width: 600px) 480px, 960px"
     alt="A tabby cat asleep on a keyboard">

<!-- With a caption -->
<figure>
  <img src="chart.png" alt="Sales rising from 2020 to 2025">
  <figcaption>Figure 1: Quarterly sales.</figcaption>
</figure>

<!-- Modern formats with fallback -->
<picture>
  <source srcset="photo.avif" type="image/avif">
  <source srcset="photo.webp" type="image/webp">
  <img src="photo.jpg" alt="A mountain at sunrise">
</picture>

Alt text rules Describe the image's purpose, not its appearance. If the image is purely decorative, use alt="" so screen readers skip it. Never omit the attribute entirely.

Lists & Tables

Unordered list

<ul>
  <li>Bread</li>
  <li>Milk</li>
  <li>Eggs</li>
</ul>

Ordered list

<ol>
  <li>Preheat the oven</li>
  <li>Mix the batter</li>
  <li>Bake for 25 minutes</li>
</ol>

<!-- Start at a different number, or reverse -->
<ol start="5" reversed> … </ol>

Description list

<dl>
  <dt>HTML</dt>
  <dd>The language that structures a web page.</dd>
  <dt>CSS</dt>
  <dd>The language that styles it.</dd>
</dl>

Nested lists

<ul>
  <li>Front end
    <ul>
      <li>HTML</li>
      <li>CSS</li>
      <li>JavaScript</li>
    </ul>
  </li>
  <li>Back end</li>
</ul>

Tables

<table>
  <caption>Monthly sales</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Revenue</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>$12,000</td>
    </tr>
    <tr>
      <th scope="row">February</th>
      <td>$15,500</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td>Total</td>
      <td>$27,500</td>
    </tr>
  </tfoot>
</table>

Spanning cells:

<td colspan="2">Spans two columns</td>
<td rowspan="3">Spans three rows</td>

Tables are for data, not layout Before CSS Flexbox and Grid, people used tables to lay out whole pages. Never do this today. Screen readers announce tables as data, which makes a table-based layout nonsense to a blind user.

Forms & Inputs

Forms collect data from the user and send it somewhere.

A complete form
<form action="/submit" method="post">

  <!-- Text input with a proper label -->
  <label for="name">Full name</label>
  <input type="text" id="name" name="name" required placeholder="Ada Lovelace">

  <!-- Email (browser validates the format) -->
  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <!-- Password -->
  <label for="pw">Password</label>
  <input type="password" id="pw" name="password" minlength="8" required>

  <!-- Number with range -->
  <label for="age">Age</label>
  <input type="number" id="age" name="age" min="0" max="120">

  <!-- Date -->
  <label for="dob">Date of birth</label>
  <input type="date" id="dob" name="dob">

  <!-- Checkbox -->
  <input type="checkbox" id="news" name="newsletter" checked>
  <label for="news">Subscribe to the newsletter</label>

  <!-- Radio group (same name = one choice) -->
  <fieldset>
    <legend>Preferred contact</legend>
    <input type="radio" id="c1" name="contact" value="email" checked>
    <label for="c1">Email</label>
    <input type="radio" id="c2" name="contact" value="phone">
    <label for="c2">Phone</label>
  </fieldset>

  <!-- Dropdown -->
  <label for="country">Country</label>
  <select id="country" name="country">
    <option value="">Choose one…</option>
    <option value="eg">Egypt</option>
    <option value="us">United States</option>
  </select>

  <!-- Multi-line text -->
  <label for="msg">Message</label>
  <textarea id="msg" name="message" rows="5"></textarea>

  <!-- File upload -->
  <label for="file">Upload a file</label>
  <input type="file" id="file" name="file" accept="image/*">

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

Input types worth knowing

TypeWhat it does
textPlain single-line text
emailValidates email shape; mobile shows @ key
passwordMasks characters
numberNumeric with spinners and min/max/step
telPhone number; mobile shows numeric keypad
urlValidates a URL
date / time / datetime-localNative date and time pickers
rangeA slider
colorA colour picker
searchText field with a clear button on some browsers
fileFile picker
checkboxOn/off, multiple can be checked
radioOne of a group
hiddenNot shown; sent with the form
submit / reset / buttonButton variants

Validation attributes

<input required>                          <!-- must not be empty -->
<input minlength="3" maxlength="20">       <!-- text length -->
<input min="0" max="100">                  <!-- numeric range -->
<input pattern="[0-9]{5}">                 <!-- regex match -->
<input type="email">                       <!-- format check -->

Always pair <label> with for Clicking the label focuses the input, screen readers announce the input's name, and it's a bigger tap target on mobile. The for attribute must match the input's id exactly.

Semantic HTML

Semantic elements describe their content. They cost nothing, help screen readers, help search engines, and make your code readable to other humans.

ElementUse forInstead of
<header>Introductory content or navigation for a page or section<div class="header">
<nav>A block of navigation links<div class="nav">
<main>The primary content. One per page.<div id="content">
<article>Self-contained content that would make sense on its own (blog post, card)<div class="post">
<section>A thematic grouping, usually with a heading<div class="section">
<aside>Tangentially related content — sidebars, pull quotes<div class="sidebar">
<footer>Footer for a page or section<div class="footer">
<figure> / <figcaption>An image, diagram, or code sample with a caption<div class="img-wrap">
<details> / <summary>A native disclosure widget — no JavaScript neededA JS accordion
<dialog>A native modal dialogA styled <div>

A typical page layout

<body>
  <header>
    <h1>My Site</h1>
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/blog">Blog</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <h2>Post title</h2>
      <p>Post content…</p>
    </article>
    <aside>
      <h3>Related</h3>
    </aside>
  </main>

  <footer>
    <p>&copy; 2026 My Site</p>
  </footer>
</body>

Native disclosure widget — no JavaScript

<details>
  <summary>What is HTML?</summary>
  <p>HTML is the language that structures a web page.</p>
</details>
What is HTML?

HTML is the language that structures a web page.

Accessibility

Roughly one in six people has a disability. Accessible HTML is not extra work — it's correct HTML.

The core checklist

  • Every <img> has a meaningful alt (or alt="" if decorative).
  • Every form input has a <label> tied by for/id.
  • Headings descend in order: h1 → h2 → h3. No skipping.
  • Colour is never the only way to convey information.
  • Text contrast is at least 4.5:1 against its background.
  • Everything interactive is reachable and usable with a keyboard alone.
  • Links describe their destination: "Read the pricing guide", not "click here".
  • Buttons are <button>, links are <a>. Don't swap them.

Keyboard focus

/* Never do this without providing a replacement */
:focus { outline: none; }   /* ✗ breaks keyboard navigation */

/* Do this instead */
:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 2px;
}

ARIA — when HTML isn't enough

ARIA attributes describe roles and states to assistive technology. The first rule of ARIA is don't use ARIA if a native element already does the job.

<!-- Unnecessary: <button> already announces itself -->
<button role="button">Save</button>

<!-- Necessary: a custom widget needs its state described -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden> … </ul>

<!-- Live region: screen readers announce changes -->
<p aria-live="polite">Saved.</p>

Skip link

<a href="#main" class="skip-link">Skip to content</a>
…
<main id="main"> … </main>
.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  position: static;
  display: inline-block;
  padding: .5rem 1rem;
  background: black;
  color: white;
}

Attributes

Attributes live inside the opening tag as name="value".

Global attributes (work on any element)

AttributePurpose
idA unique identifier on the page. Used by CSS, JS, and anchors.
classOne or more space-separated labels for styling.
styleInline CSS. Avoid except for computed values.
titleTooltip text. Not reliably read on mobile.
hiddenHide the element entirely.
langLanguage of the content.
dirText direction: ltr or rtl.
tabindexKeyboard focus order. 0 = natural, -1 = focusable by script only, positive numbers = usually a mistake.
contenteditableMakes the element editable in the browser.
data-*Your own custom data. Read via dataset in JS.

Data attributes

<!-- HTML -->
<div id="user" data-user-id="42" data-role="admin"></div>

<!-- JavaScript -->
const el = document.querySelector("#user");
console.log(el.dataset.userId);   // "42"  (kebab-case becomes camelCase)
console.log(el.dataset.role);     // "admin"

id vs class

idclass
How many per pageMust be uniqueReusable, many allowed
CSS selector#name.name
SpecificityHighLow
Typical useAnchors, JS hooks, labels' forStyling

Part III — CSS

CSS Basics

CSS works by selecting elements and declaring how they should look. A rule has a selector and a block of declarations.

selector {
  property: value;
  another-property: value;
}

/* Real example */
p {
  color: #333333;
  line-height: 1.6;
  margin-bottom: 1rem;
}

Three ways to add CSS

<!-- 1. External stylesheet — the right way -->
<link rel="stylesheet" href="styles.css">

<!-- 2. Internal — inside <style> in the <head> -->
<style>
  p { color: red; }
</style>

<!-- 3. Inline — avoid except for computed values -->
<p style="color: red;">Text</p>

Comments

/* This is a CSS comment.
   It can span multiple lines. */

Selectors

SelectorExampleSelects
Universal*Every element
TypepEvery <p>
Class.cardEvery element with class="card"
ID#headerThe one element with id="header"
Attribute[disabled]Elements with that attribute
Attribute value[type="email"]Elements with that exact value
Attribute starts with[href^="https"]Prefix match
Attribute ends with[href$=".pdf"]Suffix match
Attribute contains[class*="btn"]Substring match
Descendant.card aLinks anywhere inside .card
Child.card > aDirect children only
Adjacent siblingh2 + pThe <p> right after an <h2>
General siblingh2 ~ pAll later siblings
Groupingh1, h2, h3All three

Pseudo-classes — state-based

a:hover          { … }   /* mouse is over it */
a:focus          { … }   /* keyboard focus */
a:focus-visible  { … }   /* focus, but only when keyboard-triggered */
a:active         { … }   /* being clicked */
a:visited        { … }   /* already visited (limited properties) */
input:disabled   { … }
input:checked    { … }
input:required   { … }
input:valid      { … }
input:invalid    { … }
li:first-child   { … }
li:last-child    { … }
li:nth-child(2n) { … }   /* every 2nd: 2, 4, 6… */
li:nth-child(odd){ … }
:not(.special)   { … }   /* negation */
:is(h1, h2, h3)  { … }   /* matches any of these */
:where(h1, h2)   { … }   /* like :is but zero specificity */
:has(img)        { … }   /* parent selector — has a descendant */
:root            { … }   /* the <html> element */

Pseudo-elements — generated content and parts

p::first-line     { … }
p::first-letter   { … }
p::selection      { … }   /* highlighted text */
.box::before {
  content: "→ ";
}
.box::after {
  content: "";
  display: block;
  clear: both;
}
input::placeholder { … }
li::marker         { … }

:: vs : Pseudo-classes use one colon (:hover) and select a state. Pseudo-elements use two (::before) and select a generated or sub-part of an element. Browsers still accept one colon for the old pseudo-elements, but two is correct.

Specificity & Cascade

When two rules target the same element and property, the winner is decided by:

  1. Origin & importance — !important user styles beat author styles beat browser defaults.
  2. Specificity — how precisely the selector targets the element.
  3. Source order — later rules win ties.

Specificity score

Think of it as three numbers (A, B, C):

ColumnCounts
AIDs (#header)
BClasses, attributes, pseudo-classes (.card, [type], :hover)
CElements and pseudo-elements (p, ::before)
p                      → (0,0,1)
.card                  → (0,1,0)
.card p                → (0,1,1)
#header                → (1,0,0)
#header .card p:hover  → (1,2,1)

/* Inline styles beat everything except !important */
<p style="color: red">  → beats any selector

Keep specificity low Use single classes everywhere. If you find yourself writing #main .sidebar ul li a, you've built a trap that future-you will fall into. .sidebar-link is better.

The !important escape hatch

.hidden { display: none !important; }

Useful for utility classes that must always win. Otherwise, it's a sign your specificity is out of control. Avoid it.

Inheritance

Some properties inherit from parent to child automatically — mostly text properties: color, font-family, font-size, line-height, text-align. Box properties do not.

/* Force inheritance or reset it */
.child { color: inherit; }
.child { color: initial; }   /* back to browser default */
.child { color: unset; }     /* inherit if normally inherited, else initial */

The Box Model

Every element is a rectangle made of four layers. This one diagram fixes most beginner layout confusion.

┌─────────────── margin ────────────────┐
│  ┌───────────── border ─────────────┐  │
│  │  ┌────────── padding ─────────┐  │  │
│  │  │                            │  │  │
│  │  │         content            │  │  │
│  │  │                            │  │  │
│  │  └────────────────────────────┘  │  │
│  └──────────────────────────────────┘  │
└────────────────────────────────────────┘
LayerWhat it is
contentThe text or image itself.
paddingSpace inside the border, between border and content. Background shows through.
borderThe visible edge.
marginSpace outside the border, pushing neighbours away. Always transparent.
.box {
  width: 300px;
  padding: 20px;
  border: 5px solid black;
  margin: 30px;
}

By default (box-sizing: content-box) the element above takes up 300 + 40 + 10 = 350px of horizontal space, plus margins. This is almost never what you want.

/* Put this at the top of every stylesheet */
*, *::before, *::after {
  box-sizing: border-box;
}

With border-box, the declared width includes padding and border. The box is 300px wide, period.

Margin collapsing

Vertical margins between adjacent block elements collapse into the larger of the two, rather than adding. This surprises everyone once.

<p style="margin-bottom: 30px">A</p>
<p style="margin-top: 20px">B</p>
<!-- Gap between them is 30px, not 50px -->

Margins do not collapse when there's padding, a border, or a flex/grid container between them.

Shorthand

/* 1 value  → all four sides */
padding: 10px;

/* 2 values → vertical | horizontal */
padding: 10px 20px;

/* 3 values → top | horizontal | bottom */
padding: 10px 20px 30px;

/* 4 values → top | right | bottom | left  (clockwise) */
padding: 10px 20px 30px 40px;

/* Longhand */
padding-top: 10px;
margin-inline: auto;      /* logical property: left+right in LTR */
margin-block: 1rem;       /* logical property: top+bottom */

Display & Flow

ValueBehaviour
blockNew line, full available width. Accepts width/height.
inlineFlows with text. Width/height are ignored.
inline-blockFlows with text but accepts width/height.
noneRemoved from layout entirely. Not the same as visibility: hidden.
flexLays out children in one dimension.
gridLays out children in two dimensions.
contentsThe element itself disappears; children take its place in the layout.

display: none vs visibility: hidden vs opacity: 0

Takes up space?Read by screen readers?Clickable?
display: noneNoNoNo
visibility: hiddenYesNoNo
opacity: 0YesYesYes

Overflow

.box {
  overflow: hidden;    /* clip anything that spills out */
  overflow: auto;      /* scrollbar only if needed */
  overflow: scroll;    /* scrollbar always */
  overflow-x: auto;    /* one axis only */
  overflow-y: hidden;
}

Units & Colors

Length units

UnitRelative toBest for
pxAbsolute pixelsBorders, hairlines, fixed sizes
remThe root font size (usually 16px)Text, spacing, anything that should scale with user settings
emThe element's own font sizePadding that scales with its own text
%The parent's sizeFluid widths
vw / vh1% of viewport width / heightFull-screen sections
vmin / vmax1% of the smaller / larger viewport dimensionSquares that fit either orientation
chWidth of the "0" characterReadable line lengths
frA fraction of free space (Grid only)Grid tracks

Colors

color: red;                          /* named */
color: #ff0000;                      /* hex, 6 digits */
color: #f00;                         /* hex, 3-digit shorthand */
color: #ff000080;                    /* hex with alpha (50%) */
color: rgb(255, 0, 0);               /* rgb */
color: rgba(255, 0, 0, 0.5);         /* rgb with alpha */
color: hsl(0, 100%, 50%);            /* hue, saturation, lightness */
color: hsl(0 100% 50% / 0.5);        /* modern syntax with alpha */
color: transparent;                  /* fully transparent black */
color: currentColor;                 /* whatever the text color is */

/* Modern: define a colour once, adjust it */
:root { --brand: #0055ff; }
.box { color: color-mix(in srgb, var(--brand) 70%, white); }

HSL is easier to reason about

hsl(0,   100%, 50%)   → red
hsl(120, 100%, 50%)   → green
hsl(240, 100%, 50%)   → blue

hsl(0, 100%, 25%)     → dark red
hsl(0, 100%, 75%)     → light red
hsl(0,  20%, 50%)     → desaturated red (greyish)

Gradients

background: linear-gradient(to right, red, blue);
background: linear-gradient(135deg, #000 0%, #fff 100%);
background: radial-gradient(circle, red, blue);
background: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);

Typography

body {
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               Helvetica, Arial, sans-serif;
  font-size: 1rem;          /* 16px by default */
  line-height: 1.6;         /* unitless: 1.6 × font-size */
  font-weight: 400;
  letter-spacing: 0;
  color: #222;
}

h1 {
  font-size: clamp(1.8rem, 5vw, 3rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

code, pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

Font stacks

Always list several fonts, ending with a generic family. The browser uses the first one available.

/* System UI stack — fast, native-looking, no download */
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

/* Serif body text */
font-family: Georgia, "Times New Roman", serif;

/* Monospace */
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

Web fonts

@font-face {
  font-family: "My Font";
  src: url("my-font.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;   /* show fallback text immediately */
}

/* Or via a service */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");

Line length Aim for 45–75 characters per line. Longer than that and the eye loses its place returning to the next line. max-width: 65ch is a good default for prose.

Other useful text properties

text-align: left | right | center | justify;
text-decoration: underline | line-through | none;
text-transform: uppercase | lowercase | capitalize;
text-indent: 2rem;
text-shadow: 1px 1px 2px rgba(0,0,0,.3);
word-break: break-word;
overflow-wrap: break-word;    /* break long URLs */
white-space: nowrap | pre | pre-wrap;
text-overflow: ellipsis;      /* needs overflow: hidden + nowrap */
font-variant-numeric: tabular-nums;  /* aligned digits */

Flexbox

Flexbox lays out items along a single axis — a row or a column. It's the default tool for navigation bars, card rows, centering, and distributing space.

.container {
  display: flex;
  flex-direction: row;        /* row | row-reverse | column | column-reverse */
  flex-wrap: nowrap;          /* nowrap | wrap | wrap-reverse */
  justify-content: flex-start;/* main axis alignment */
  align-items: stretch;       /* cross axis alignment */
  align-content: normal;      /* multi-line cross axis */
  gap: 1rem;                  /* space between items */
}

justify-content — main axis

flex-start      [■■■        ]
flex-end        [        ■■■]
center          [    ■■■    ]
space-between   [■    ■    ■]
space-around    [ ■  ■  ■  ■ ]
space-evenly    [  ■  ■  ■  ]

align-items — cross axis

stretch         items fill the container height
flex-start      items sit at the top
flex-end        items sit at the bottom
center          items are vertically centred
baseline        items align on their text baseline

Flex item properties

.item {
  flex-grow: 1;      /* how much spare space this item takes */
  flex-shrink: 1;    /* how much it shrinks when space is tight */
  flex-basis: auto;  /* its starting size */

  /* Shorthand: grow shrink basis */
  flex: 1;           /* = 1 1 0    — equal widths */
  flex: auto;        /* = 1 1 auto — grow but respect content */
  flex: none;        /* = 0 0 auto — fixed size */

  align-self: center;  /* override the container's align-items */
  order: 2;            /* reorder visually (doesn't change DOM order) */
}

The classic centering trick

.center-everything {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

A navigation bar

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 2px solid black;
}
.navbar ul {
  display: flex;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

Flexbox gap gap works in Flexbox in all modern browsers. It's far better than the old negative-margin hacks.

Grid

Grid lays out items in two dimensions — rows and columns. Use it for page layouts, galleries, and anything where Flexbox would need nesting.

.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px;   /* sidebar | main | sidebar */
  grid-template-rows: auto 1fr auto;         /* header | body | footer */
  gap: 1rem;
}

Defining tracks

/* Three equal columns */
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: repeat(3, 1fr);

/* Fixed + fluid */
grid-template-columns: 250px 1fr;

/* Responsive without a media query — auto-fit + minmax */
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));

/* Named areas */
grid-template-areas:
  "header header"
  "sidebar main"
  "footer footer";

Placing items

.item {
  grid-column: 1 / 3;      /* from line 1 to line 3 (spans 2 columns) */
  grid-column: span 2;     /* same thing, relative */
  grid-row: 2 / 4;
}

/* With named areas */
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

Alignment

.container {
  justify-items: stretch;   /* align items within their cells (inline axis) */
  align-items: stretch;     /* (block axis) */
  justify-content: start;   /* align the whole track set */
  align-content: start;
  place-items: center;      /* shorthand for both */
}

A complete page layout

.page {
  display: grid;
  grid-template-areas:
    "header header"
    "nav    main"
    "footer footer";
  grid-template-columns: 220px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
  min-height: 100vh;
}

@media (max-width: 700px) {
  .page {
    grid-template-areas:
      "header"
      "nav"
      "main"
      "footer";
    grid-template-columns: 1fr;
  }
}

Flexbox or Grid? Flexbox for one dimension (a row of buttons, a column of cards). Grid for two (a whole page layout). They work together — a Grid item can be a Flex container.

Positioning

ValueBehaviour
staticDefault. Normal document flow. top/left/right/bottom are ignored.
relativeStays in flow, but can be nudged with offsets. Also creates a positioning context for children.
absoluteRemoved from flow. Positioned relative to the nearest positioned ancestor.
fixedRemoved from flow. Positioned relative to the viewport. Stays put on scroll.
stickyBehaves like relative until it hits a threshold, then sticks.
/* Positioning context + absolutely positioned child */
.card {
  position: relative;
}
.badge {
  position: absolute;
  top: 0;
  right: 0;
}

/* Full-screen overlay */
.overlay {
  position: fixed;
  inset: 0;             /* shorthand for top/right/bottom/left: 0 */
  background: rgba(0,0,0,.5);
}

/* Sticky header */
.header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: white;
}

Stacking and z-index

z-index only works on positioned elements (or flex/grid items). Higher numbers paint on top. Elements with the same z-index follow DOM order.

.modal   { position: fixed; z-index: 1000; }
.tooltip { position: absolute; z-index: 1001; }

Avoid z-index wars Keep a small, documented scale — 1, 10, 100, 1000. If you're writing z-index: 99999, something has gone wrong earlier.

Responsive Design

Over half of all web traffic is mobile. Responsive design means one page that adapts to any screen.

<!-- Always include this -->
<meta name="viewport" content="width=device-width, initial-scale=1">

Mobile-first media queries

/* Base styles target small screens */
.container {
  padding: 1rem;
  font-size: 1rem;
}

/* Layer on as the screen grows */
@media (min-width: 600px) {
  .container { padding: 2rem; }
}

@media (min-width: 900px) {
  .container { max-width: 860px; margin: 0 auto; }
}

@media (min-width: 1200px) {
  .container { max-width: 1100px; }
}

Mobile-first means you add complexity as space allows, rather than stripping it away. It produces simpler CSS and better performance on phones.

Other media features

@media (min-width: 600px) and (max-width: 899px) { … }
@media (orientation: landscape) { … }
@media (prefers-color-scheme: dark) { … }
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
@media (hover: hover) { … }        /* only devices with a real pointer */
@media print { … }

Fluid values with clamp()

/* clamp(minimum, preferred, maximum) */
h1 {
  font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}

.section {
  padding: clamp(1rem, 5vw, 4rem);
}

Responsive images

img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modern aspect ratio box */
.thumb {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  width: 100%;
}

Transitions & Animations

Transitions

.button {
  background: black;
  color: white;
  transition: background-color 0.2s ease, transform 0.15s ease;
}

.button:hover {
  background: #333;
  transform: translateY(-2px);
}
/* Shorthand: property duration timing-function delay */
transition: all 0.3s ease-in-out 0.1s;

/* Timing functions */
ease | linear | ease-in | ease-out | ease-in-out
cubic-bezier(0.68, -0.55, 0.27, 1.55)   /* custom */

Animations with @keyframes

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: fadeInUp 0.5s ease-out both;
}

/* Full shorthand */
animation-name: fadeInUp;
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0s;
animation-iteration-count: infinite;   /* or a number */
animation-direction: alternate;
animation-fill-mode: both;             /* keep start/end state */
animation-play-state: running | paused;

Spinner

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 32px;
  height: 32px;
  border: 4px solid #ddd;
  border-top-color: black;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

Transforms

transform: translate(10px, 20px);
transform: translateX(-50%);
transform: scale(1.2);
transform: rotate(45deg);
transform: skew(10deg, 5deg);
transform: translate(-50%, -50%) scale(1.1);   /* chained */

transform-origin: center center;
transform-style: preserve-3d;
perspective: 800px;

Respect reduced motion Some people get motion sick from animations. Always provide an opt-out:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Custom Properties (CSS Variables)

Define a value once, reuse it everywhere, and change it at runtime from JavaScript or a media query.

:root {
  --brand: #0055ff;
  --text: #111111;
  --bg: #ffffff;
  --space: 1rem;
  --radius: 8px;
  --shadow: 0 2px 8px rgba(0,0,0,.1);
}

.card {
  background: var(--bg);
  color: var(--text);
  padding: var(--space);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* Fallback if the variable is undefined */
.card { color: var(--text, black); }

Scoped variables

/* Override just inside this component */
.dark-section {
  --bg: #111;
  --text: #eee;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000;
    --text: #fff;
  }
}

/* Toggle via a class */
[data-theme="dark"] {
  --bg: #000;
  --text: #fff;
}

Changing them from JavaScript

// Set on the root
document.documentElement.style.setProperty("--brand", "#ff0000");

// Read it back
const value = getComputedStyle(document.documentElement)
  .getPropertyValue("--brand");

Variables vs preprocessor variables Sass variables are resolved at build time and can't change at runtime. CSS custom properties are live — they cascade, they inherit, and JS can read and write them.

Modern CSS Features

Logical properties

Write once, work in both left-to-right and right-to-left languages.

/* Instead of */          /* Use */
margin-left                margin-inline-start
margin-right               margin-inline-end
margin-top                 margin-block-start
margin-bottom              margin-block-end
padding-left               padding-inline-start
width                      inline-size
height                     block-size
border-left                border-inline-start

:is(), :where(), and :has()

/* Group without repeating */
:is(h1, h2, h3) { line-height: 1.2; }

/* Same, but zero specificity — easy to override */
:where(h1, h2, h3) { margin-top: 2rem; }

/* Style a parent based on its children */
.card:has(img) { padding-top: 0; }
form:has(:invalid) { border-color: red; }

Container queries

Style based on the size of a container, not the viewport. Perfect for reusable components.

.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  .card { display: flex; gap: 1rem; }
}

Nesting

.card {
  padding: 1rem;
  background: white;

  & h2 {
    margin-top: 0;
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,.1);
  }

  &.featured {
    border: 2px solid black;
  }
}

aspect-ratio, gap, and inset

.video  { aspect-ratio: 16 / 9; }
.grid   { gap: 1rem; }
.overlay{ position: fixed; inset: 0; }

scroll-behavior and scroll snapping

html { scroll-behavior: smooth; }

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.carousel > * {
  scroll-snap-align: start;
  flex: 0 0 100%;
}

Part IV — JavaScript

JavaScript Basics

JavaScript is a real programming language that runs in the browser (and, via Node.js, on the server). It lets you react to clicks, fetch data, and change the page after it has loaded.

Adding JavaScript to a page

<!-- External — preferred -->
<script src="app.js" defer></script>

<!-- Inline, inside <script> -->
<script>
  console.log("Hello");
</script>

Declaring variables

const name = "Ada";       // can't be reassigned. Use by default.
let age = 36;             // can be reassigned. Use when you must.
var old = true;           // legacy, function-scoped. Avoid.

// const prevents reassignment, not mutation:
const list = [1, 2];
list.push(3);             // ✓ fine, the array changed
// list = [4, 5];         // ✗ TypeError

Output

console.log("Hello");        // browser console
console.error("Problem");    // red, with a stack trace
console.warn("Careful");     // yellow
console.table([{a:1},{a:2}]);
console.time("loop"); /* … */ console.timeEnd("loop");

alert("Hi");                 // blocking popup — avoid
document.title = "New tab title";

Types & Coercion

The primitive types

TypeExamplestypeof returns
String"hello", 'hi', `tpl`"string"
Number42, 3.14, -0, NaN, Infinity"number"
BigInt9007199254740993n"bigint"
Booleantrue, false"boolean"
Undefinedundefined"undefined"
Nullnull"object" (historical bug)
SymbolSymbol("id")"symbol"
Object{}, [], function(){}"object" / "function"

undefined vs null

let x;                    // undefined — never assigned
function f() {}           // f() returns undefined

const y = null;           // null — deliberately empty

x == null                 // true  (loose: matches both)
x === null                // false
y === null                // true

Truthy and falsy

These eight values are falsy. Everything else is truthy.

false
0
-0
0n            (BigInt zero)
""            (empty string)
null
undefined
NaN
if ("0")   { }   // runs — non-empty string is truthy
if ([])    { }   // runs — empty array is truthy
if ({})    { }   // runs — empty object is truthy

// A safe "is this set?" check
if (value) { }              // falsy check
if (value != null) { }      // defined and not null

Type coercion

"5" + 3        // "53"   (+ prefers string concatenation)
"5" - 3        // 2      (- forces numbers)
"5" * "2"      // 10
1 + true       // 2
1 + "1"        // "11"
[] + []        // ""
[] + {}        // "[object Object]"
0.1 + 0.2      // 0.30000000000000004  (floating point)

// Convert explicitly
Number("42")        // 42
Number("abc")       // NaN
parseInt("42px")    // 42
parseFloat("3.14")  // 3.14
String(42)          // "42"
Boolean(0)          // false
(42).toString()     // "42"

Operators

Arithmetic

5 + 2     // 7
5 - 2     // 3
5 * 2     // 10
5 / 2     // 2.5
5 % 2     // 1   (remainder / modulo)
5 ** 2    // 25  (exponent)

let n = 5;
n += 2;   // 7
n -= 2;   // 5
n *= 2;   // 10
n /= 2;   // 5
n++;      // increment by 1
n--;      // decrement by 1

Comparison

==    loose equality  — coerces types. Avoid.
===   strict equality — no coercion. Use this.
!=    loose inequality
!==   strict inequality
> < >= <=   comparisons

"1" == 1     // true
"1" === 1    // false
NaN === NaN  // false  (use Number.isNaN())
Object.is(a, b)   // like === but NaN equals NaN, and -0 ≠ +0

Logical

a && b     // b if a is truthy, otherwise a
a || b     // a if a is truthy, otherwise b
!a         // boolean negation
!!a        // coerce to boolean
a ?? b     // b only if a is null or undefined (nullish coalescing)

// Optional chaining — safe access
user?.address?.city     // undefined instead of a TypeError
arr?.[0]
fn?.()

// Nullish assignment
x ??= 5;      // assign if x is null/undefined
x ||= 5;      // assign if x is falsy
x &&= 5;      // assign if x is truthy

Ternary

const label = age >= 18 ? "Adult" : "Minor";

Spread and rest

// Spread: expand an iterable
const a = [1, 2, 3];
const b = [...a, 4, 5];          // [1,2,3,4,5]
const o = { ...{x:1}, y: 2 };    // {x:1, y:2}

// Rest: collect the remainder
function sum(...nums) {
  return nums.reduce((t, n) => t + n, 0);
}
const [first, ...others] = [1, 2, 3];
const { id, ...rest } = { id: 1, a: 2, b: 3 };

Control Flow

if (score >= 90) {
  grade = "A";
} else if (score >= 80) {
  grade = "B";
} else {
  grade = "C";
}

switch (day) {
  case "sat":
  case "sun":
    console.log("Weekend");
    break;
  default:
    console.log("Weekday");
}

// Guard clause — return early instead of nesting
function process(user) {
  if (!user) return;
  if (!user.active) return;
  // …the real work
}

Functions

// Declaration — hoisted, so callable before its definition
function greet(name) {
  return `Hello, ${name}!`;
}

// Expression — not hoisted
const greet2 = function (name) {
  return `Hello, ${name}!`;
};

// Arrow — no own `this`, concise
const greet3 = (name) => `Hello, ${name}!`;

// Arrow with a block body
const greet4 = (name) => {
  const upper = name.toUpperCase();
  return `Hello, ${upper}!`;
};

// Default parameters
function greet5(name = "stranger") {
  return `Hello, ${name}!`;
}

// Rest parameters
function total(...nums) {
  return nums.reduce((sum, n) => sum + n, 0);
}

Return values

function noReturn() { }        // returns undefined
function early(x) {
  if (x < 0) return;           // bare return → undefined
  return x * 2;
}

// Return multiple values via an array or object
function minMax(arr) {
  return [Math.min(...arr), Math.max(...arr)];
}
const [lo, hi] = minMax([3, 1, 4, 1, 5]);

Closures

function makeCounter() {
  let count = 0;              // private to the returned function
  return function () {
    count += 1;
    return count;
  };
}

const next = makeCounter();
next();   // 1
next();   // 2
next();   // 3

Callbacks and higher-order functions

function repeat(n, action) {
  for (let i = 0; i < n; i++) action(i);
}

repeat(3, (i) => console.log("tick", i));

// Functions are values
const operations = {
  add: (a, b) => a + b,
  sub: (a, b) => a - b,
};
operations.add(2, 3);   // 5

Arrays

const fruits = ["apple", "banana", "cherry"];

fruits[0]           // "apple"
fruits.length       // 3
fruits.at(-1)       // "cherry"  (last item)
Array.isArray(fruits)  // true

Mutating methods

MethodDoesReturns
push(x)Add to the endNew length
pop()Remove from the endRemoved item
unshift(x)Add to the startNew length
shift()Remove from the startRemoved item
splice(i, n, …)Remove/insert at indexRemoved items
reverse()Reverse in placeThe array
sort(fn)Sort in placeThe array
fill(v)Fill with a valueThe array

Non-mutating methods

MethodDoes
slice(a, b)Copy a portion
concat(arr)Join arrays
join(sep)Array → string
indexOf(x) / lastIndexOf(x)Find an index, or −1
includes(x)Boolean membership test
find(fn) / findIndex(fn)First match
some(fn) / every(fn)Any / all match
flat() / flatMap(fn)Flatten nested arrays
toSorted() / toReversed()Non-mutating sort/reverse

The big three: map, filter, reduce

const nums = [1, 2, 3, 4, 5];

// map — transform each item, same length
const doubled = nums.map((n) => n * 2);       // [2,4,6,8,10]

// filter — keep items that pass a test
const evens = nums.filter((n) => n % 2 === 0); // [2,4]

// reduce — boil down to one value
const sum = nums.reduce((total, n) => total + n, 0);  // 15

// Chaining
const result = nums
  .filter((n) => n % 2 === 1)
  .map((n) => n * 10)
  .reduce((a, b) => a + b, 0);   // 10 + 30 + 50 = 90

Destructuring and iteration

const [a, b, ...rest] = [1, 2, 3, 4];
// a=1, b=2, rest=[3,4]

for (const fruit of fruits) {
  console.log(fruit);
}

fruits.forEach((fruit, i) => {
  console.log(i, fruit);
});

Sorting correctly

// ✗ Wrong: sorts as strings
[10, 9, 1].sort();              // [1, 10, 9]

// ✓ Correct: numeric comparator
[10, 9, 1].sort((a, b) => a - b);   // [1, 9, 10]
[10, 9, 1].sort((a, b) => b - a);   // [10, 9, 1]

// Sort objects by a property
users.sort((a, b) => a.name.localeCompare(b.name));

Objects

const user = {
  name: "Ada",
  age: 36,
  email: "ada@example.com",
  greet() {
    return `Hi, I'm ${this.name}`;
  },
  "favourite colour": "blue",   // quoted key
};

// Access
user.name                 // dot notation
user["favourite colour"]  // bracket notation, for odd keys
user.greet()              // method call

// Add / change / delete
user.city = "London";
delete user.age;

// Check existence
"name" in user               // true
user.hasOwnProperty("name")  // true

Destructuring

const { name, age } = user;
const { name: userName, age: userAge = 0 } = user;   // rename + default

// Nested
const { address: { city } = {} } = user;

// In function parameters
function display({ name, age }) {
  return `${name}, ${age}`;
}
display(user);

Useful methods

Object.keys(user)      // ["name", "age", …]
Object.values(user)    // ["Ada", 36, …]
Object.entries(user)   // [["name","Ada"], ["age",36], …]

Object.assign({}, a, b)  // merge (mutates the first)
{ ...a, ...b }           // merge (creates new)

Object.freeze(user)      // make immutable
Object.fromEntries([["a",1],["b",2]])   // {a:1, b:2}

Iterating

for (const key in user) {
  console.log(key, user[key]);
}

for (const [key, value] of Object.entries(user)) {
  console.log(key, value);
}

JSON

const text = JSON.stringify(user);       // object → string
const obj  = JSON.parse(text);           // string → object

// Pretty-print
JSON.stringify(user, null, 2);

// JSON has no functions, undefined, or comments.
// Dates become strings.

Strings

const s = "Hello, world";

s.length              // 12
s[0]                  // "H"
s.toUpperCase()       // "HELLO, WORLD"
s.toLowerCase()
s.trim()              // strip whitespace from both ends
s.includes("world")   // true
s.startsWith("Hello") // true
s.endsWith("world")   // true
s.indexOf("o")        // 4  (or -1)
s.slice(0, 5)         // "Hello"
s.substring(7)        // "world"
s.replace("world", "there")
s.replaceAll("l", "L")
s.split(", ")         // ["Hello", "world"]
s.repeat(2)
s.padStart(20, "-")
s.charAt(0)
s.charCodeAt(0)

// Template literals
const name = "Ada";
const msg = `Hello, ${name}! You have ${2 + 3} messages.`;
const multi = `line one
line two`;

Regular expressions

const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = "Date: 2026-10-12".match(re);
// match[0] = "2026-10-12", match[1] = "2026", …

/hello/i.test("Hello")       // true (case-insensitive)
"a,b,c".split(/,/)
"a1b2c3".replace(/\d/g, "")  // "abc"
"  hi  ".replace(/\s+/g, " ") // " hi "

// Named groups
const m = "2026-10-12".match(/(?<y>\d{4})-(?<m>\d{2})/);
m.groups.y;   // "2026"

Loops & Iteration

// Classic for
for (let i = 0; i < 5; i++) {
  console.log(i);
}

// for…of — values of an iterable
for (const item of ["a", "b", "c"]) {
  console.log(item);
}

// for…in — keys of an object (avoid on arrays)
for (const key in { a: 1, b: 2 }) {
  console.log(key);
}

// while
let n = 0;
while (n < 5) {
  n++;
}

// do…while — runs at least once
do {
  n--;
} while (n > 0);

// break and continue
for (const x of [1, 2, 3, 4]) {
  if (x === 2) continue;   // skip this iteration
  if (x === 4) break;      // exit the loop
  console.log(x);
}

DOM Manipulation

The Document Object Model is the live tree of objects the browser builds from your HTML. JavaScript edits this tree; the browser repaints.

Finding elements

document.getElementById("header")
document.querySelector(".card")             // first match
document.querySelectorAll(".card")          // NodeList of all
document.getElementsByClassName("card")     // live HTMLCollection
document.getElementsByTagName("p")          // live HTMLCollection

// Scoped search
const card = document.querySelector(".card");
card.querySelector("a");

// Walking the tree
el.parentElement
el.children
el.firstElementChild
el.lastElementChild
el.nextElementSibling
el.previousElementSibling

Reading and changing content

el.textContent          // all text, ignoring markup — safe
el.innerText            // visible text only (forces layout)

el.innerHTML = "<b>Hi</b>";   // parses HTML — XSS risk with user input

el.setAttribute("data-id", "42");
el.getAttribute("data-id");
el.removeAttribute("data-id");
el.dataset.id           // same as the data-id attribute

el.style.color = "red";
el.style.setProperty("--brand", "#f00");

el.classList.add("active");
el.classList.remove("active");
el.classList.toggle("active");
el.classList.contains("active");
el.classList.replace("a", "b");

Never use innerHTML with user input It executes scripts. Use textContent for text, or document.createElement() when you need structure.

Creating and inserting elements

const li = document.createElement("li");
li.textContent = "New item";
li.classList.add("item");

list.appendChild(li);              // add at the end
list.prepend(li);                  // add at the start
list.append(li, "some text");      // append multiple things
list.before(li);                   // insert before the list
list.after(li);                    // insert after

li.remove();                       // delete it
li.replaceWith(other);             // swap it out
parent.innerHTML = "";             // nuke all children

Attributes vs properties

// Attributes are what's written in the HTML
input.getAttribute("value")     // the original value

// Properties are the live state
input.value                     // what's currently typed

// For most things, use the property.
checkbox.checked = true;
input.value = "new text";
link.href = "https://example.com";

Events

const btn = document.querySelector("#my-button");

btn.addEventListener("click", (event) => {
  console.log("Clicked!", event.target);
});

// Named handler, so it can be removed
function handleClick(e) { console.log("clicked"); }
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);

The event object

element.addEventListener("click", (e) => {
  e.target          // the element that was clicked
  e.currentTarget   // the element the listener is attached to
  e.type            // "click"
  e.preventDefault()  // stop the default browser behaviour
  e.stopPropagation() // stop the event bubbling up
  e.clientX, e.clientY  // mouse position
  e.key             // for keyboard events
});

Common events

CategoryEvents
Mouseclick, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave, contextmenu
Keyboardkeydown, keyup, keypress (deprecated)
Formsubmit, reset, change, input, focus, blur, invalid
DocumentDOMContentLoaded, load, resize, scroll, visibilitychange
Touch / Pointertouchstart, touchend, pointerdown, pointerup, pointermove
Mediaplay, pause, ended, timeupdate
Dragdragstart, dragover, drop

Event delegation

Attach one listener to a parent instead of many to children. Works for dynamically added elements too.

document.querySelector("ul").addEventListener("click", (e) => {
  const li = e.target.closest("li");
  if (!li) return;
  console.log("Clicked item:", li.textContent);
});

Waiting for the DOM

document.addEventListener("DOMContentLoaded", () => {
  // safe to query elements here
});

// Or use defer on the script tag — simpler and preferred
// <script src="app.js" defer></script>

Keyboard and form example

const input = document.querySelector("#search");

input.addEventListener("input", (e) => {
  console.log("Current value:", e.target.value);
});

input.addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    e.preventDefault();
    runSearch(e.target.value);
  }
});

form.addEventListener("submit", (e) => {
  e.preventDefault();      // stop the page reload
  const data = new FormData(form);
  console.log(Object.fromEntries(data));
});

Live demo

Clicks: 0
let clicks = 0;
const btn = document.querySelector("#demo-counter");
const out = document.querySelector("#demo-output");

btn.addEventListener("click", () => {
  clicks += 1;
  out.textContent = "Clicks: " + clicks;
});

Async & Promises

JavaScript runs on a single thread. Long operations — network requests, timers, file reads — are handed off and their results delivered later via callbacks.

Callbacks

setTimeout(() => {
  console.log("2 seconds later");
}, 2000);

setInterval(() => {
  console.log("every second");
}, 1000);

// Deep nesting is the "callback pyramid of doom" — avoid

Promises

const p = new Promise((resolve, reject) => {
  const ok = true;
  if (ok) resolve("Success!");
  else reject(new Error("Failed"));
});

p.then((value) => console.log(value))
 .catch((err) => console.error(err))
 .finally(() => console.log("done"));

// Promise combinators
Promise.all([p1, p2, p3])      // all must succeed; rejects on first failure
Promise.allSettled([p1, p2])   // waits for all, never rejects
Promise.race([p1, p2])         // first to settle wins
Promise.any([p1, p2])          // first to succeed wins

async / await

async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const user = await response.json();
    return user;
  } catch (error) {
    console.error("Failed to load user:", error);
    throw error;   // re-throw if the caller should know
  }
}

// An async function always returns a Promise
getUser(1).then((user) => console.log(user.name));

Running things in parallel

// ✗ Sequential — 3 round trips, one after another
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

// ✓ Parallel — one round trip's worth of waiting
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

The event loop JavaScript has one call stack and a task queue. Synchronous code runs to completion; when the stack empties, the loop takes the next task from the queue. That's why setTimeout(fn, 0) still runs after the current code finishes.

Fetch & APIs

An API is a defined way for one program to talk to another. A web API exposes endpoints — URLs that return data, usually as JSON.

GET request

async function loadUsers() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  const users = await response.json();
  return users;
}

loadUsers()
  .then((users) => console.log(users))
  .catch((err) => console.error(err));

POST request with a JSON body

async function createUser(data) {
  const response = await fetch("/api/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
    },
    body: JSON.stringify(data),
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

Other response formats

await response.json()      // parse JSON
await response.text()      // plain text / HTML
await response.blob()      // binary (images, files)
await response.formData()  // multipart form data
await response.arrayBuffer()  // raw bytes

response.status            // 200
response.ok                // true if 200–299
response.headers.get("content-type")

Timeouts and cancellation

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);

try {
  const res = await fetch(url, { signal: controller.signal });
  const data = await res.json();
} catch (err) {
  if (err.name === "AbortError") console.log("Timed out");
} finally {
  clearTimeout(timer);
}

Common HTTP methods (recap)

MethodMeansCRUD
GETReadR
POSTCreateC
PUT / PATCHReplace / updateU
DELETERemoveD

Error Handling

try {
  riskyOperation();
} catch (error) {
  console.error(error.name);     // "TypeError"
  console.error(error.message);  // human-readable
  console.error(error.stack);    // where it happened
} finally {
  cleanup();   // always runs
}

Built-in error types

TypeWhen
TypeErrorWrong type — calling a non-function, reading a property of null
ReferenceErrorUsing an undeclared variable
SyntaxErrorInvalid code — caught before running
RangeErrorValue out of range
URIErrorMalformed URI
AggregateErrorMultiple errors from Promise.any()

Throwing your own

function divide(a, b) {
  if (b === 0) throw new Error("Cannot divide by zero");
  return a / b;
}

// Custom error class
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

throw new ValidationError("email", "That doesn't look like an email");

Global handlers

window.addEventListener("error", (e) => {
  console.error("Uncaught:", e.message);
});

window.addEventListener("unhandledrejection", (e) => {
  console.error("Unhandled promise rejection:", e.reason);
});

Modern JavaScript (ES6+)

FeatureExample
let / constBlock-scoped variables
Arrow functions(x) => x * 2
Template literals`Hello ${name}`
Destructuringconst { a, b } = obj;
Default parametersfunction f(x = 1) {}
Rest / spread...args, [...arr]
Classesclass Foo extends Bar {}
Modulesimport / export
Promises & async/awaitawait fetch(url)
Optional chaininguser?.address?.city
Nullish coalescingvalue ?? "default"
Array.prototype.flat[[1],[2]].flat()
Object shorthand{ name, age }
Computed keys{ [key]: value }
Map / SetKeyed collections with better semantics than objects/arrays
for…ofIterate values instead of indices

Map and Set

const m = new Map();
m.set("a", 1);
m.get("a");        // 1
m.has("a");        // true
m.size;            // 1

const s = new Set([1, 2, 2, 3]);
s.size;            // 3
s.has(2);          // true

// Dedupe an array
const unique = [...new Set([1, 1, 2, 3, 3])];   // [1,2,3]

Modules

// math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export default function multiply(a, b) {
  return a * b;
}

// app.js
import multiply, { PI, add } from "./math.js";
import * as math from "./math.js";

console.log(add(1, 2));
console.log(math.PI);

In the browser, load a module with type="module":

<script type="module" src="app.js"></script>

Modules are deferred by default They run after the HTML is parsed, and they always run in strict mode. Each module has its own scope — no globals leaking.

Classes

class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  speak() {
    return `${this.name} says ${this.sound}`;
  }

  get upperName() {
    return this.name.toUpperCase();
  }

  static create(name) {
    return new Animal(name, "…");
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name, "woof");
    this.legs = 4;
  }

  speak() {
    return super.speak() + "!";   // call the parent version
  }
}

const d = new Dog("Rex");
d.speak();          // "Rex says woof!"
d instanceof Animal // true

Classes are sugar Under the hood they're prototype-based. The this keyword refers to the instance — except in arrow functions, which inherit it from the enclosing scope. That's why methods used as callbacks often need .bind(this) or an arrow wrapper.

Part V — The Browser

How Browsers Render

  1. Parse HTML → build the DOM tree.
  2. Parse CSS → build the CSSOM tree.
  3. Combine them into a render tree (visible elements only).
  4. Layout (reflow) — compute the position and size of every box.
  5. Paint — fill in pixels.
  6. Composite — combine layers, often on the GPU.

Steps 4–6 are expensive. Changing a width triggers layout, paint, and composite. Changing opacity or transform can skip straight to composite — which is why animations of those properties are fast.

Blocking resources

  • CSS blocks rendering. Nothing paints until the stylesheet loads.
  • JavaScript blocks parsing. The HTML parser stops when it hits a plain <script>.
  • async downloads in parallel and runs as soon as it's ready (order not guaranteed).
  • defer downloads in parallel and runs after parsing, in order.
<script src="a.js"></script>          <!-- blocks parsing -->
<script src="a.js" async></script>    <!-- parallel, order not kept -->
<script src="a.js" defer></script>    <!-- parallel, order kept, runs last -->

Reflow vs repaint

ChangeTriggers
width, height, font-size, marginLayout + paint + composite (slow)
color, background, visibilityPaint + composite
transform, opacityComposite only (fast)

DevTools

Open with F12, Ctrl+Shift+I, or right-click → Inspect.

PanelUse it for
ElementsInspect the DOM, edit HTML and CSS live, see the box model and computed styles.
ConsoleRun JavaScript, read errors and logs.
SourcesSet breakpoints, step through code, inspect variables.
NetworkSee every request: status, size, timing, headers, response body. Throttle to slow 3G.
PerformanceRecord a session and find what's blocking the main thread.
ApplicationInspect cookies, localStorage, sessionStorage, IndexedDB, service workers.
LighthouseAudit performance, accessibility, SEO, and best practices.
Device ToolbarSimulate screen sizes and mobile devices (Ctrl+Shift+M).

Debugging with breakpoints

// In Sources, click a line number to set a breakpoint.
// Or write it directly in code:
debugger;

// Console API
console.log(...)
console.table(arr)
console.dir(obj)          // inspect as a JS object
console.group("Label")    // collapsible group
console.groupEnd()
console.count("calls")
console.trace()           // print the call stack

Useful console tricks

// $ = querySelector, $$ = querySelectorAll (in Chrome/Firefox)
$(".card");
$$(".card");

// Inspect the last value
$_

// Copy an object to the clipboard
copy(someObject)

Storage

MechanismSizeLifetimeSent to server?
Cookies~4 KB eachSet expiry; can be session-onlyYes, with every request
localStorage~5–10 MBUntil clearedNo
sessionStorage~5–10 MBUntil the tab closesNo
IndexedDBHundreds of MBUntil clearedNo
Cache APILargeUntil clearedNo

localStorage

// Strings only — serialize objects yourself
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");        // "dark"
localStorage.removeItem("theme");
localStorage.clear();

// Storing an object
localStorage.setItem("user", JSON.stringify({ id: 1, name: "Ada" }));
const user = JSON.parse(localStorage.getItem("user"));

Don't store secrets localStorage is readable by any script on the page, including one injected via an XSS flaw. Never put tokens you can't afford to leak there.

Cookies

// Set from JavaScript (rarely the right approach)
document.cookie = "name=Ada; max-age=86400; path=/; SameSite=Lax";

// Read
document.cookie;   // "name=Ada; other=value"

Cookie attributes:

AttributeMeaning
SecureOnly sent over HTTPS
HttpOnlyNot readable by JavaScript (protects against XSS)
SameSite=Strict|Lax|NoneControls whether it's sent on cross-site requests (protects against CSRF)
Max-Age / ExpiresWhen it's deleted
Path / DomainWhich URLs it applies to

Performance

Core Web Vitals

MetricMeasuresTarget
LCP — Largest Contentful PaintWhen the main content appears< 2.5 s
INP — Interaction to Next PaintResponsiveness to input< 200 ms
CLS — Cumulative Layout ShiftHow much the page jumps around< 0.1

Practical wins

  • Compress and resize images. Serve WebP or AVIF.
  • Add width and height to every <img> to prevent layout shift.
  • Lazy-load offscreen images: <img loading="lazy">.
  • Minify and compress text assets (gzip/Brotli).
  • Defer non-critical JavaScript.
  • Cache aggressively with Cache-Control headers.
  • Animate only transform and opacity.
  • Avoid layout thrashing — batch reads then writes.
// ✗ Layout thrashing: read, write, read, write…
for (const el of els) {
  el.style.height = el.offsetHeight + 10 + "px";
}

// ✓ Batch: read everything, then write everything
const heights = els.map((el) => el.offsetHeight);
els.forEach((el, i) => { el.style.height = heights[i] + 10 + "px"; });

Measuring

console.time("work");
doExpensiveThing();
console.timeEnd("work");

performance.now();   // high-resolution timestamp

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
  }
}).observe({ type: "largest-contentful-paint", buffered: true });

Part VI — Tooling & Workflow

Editors

VS Code is the default choice. Other options: WebStorm (paid, powerful), Sublime Text (fast, minimal), Neovim (terminal-based, steep curve).

VS Code essentials

ShortcutDoes
Ctrl+PQuick-open a file
Ctrl+Shift+PCommand palette
Ctrl+/Toggle comment
Alt+↑/↓Move line
Shift+Alt+↓Duplicate line
Ctrl+DSelect next occurrence
Ctrl+Shift+FSearch across files
F2Rename symbol
Ctrl+`Toggle integrated terminal

Extensions worth installing

  • Live Server — instant-reload dev server for static files.
  • Prettier — automatic formatting on save.
  • ESLint — flags problems as you type.
  • Error Lens — shows errors inline.
  • Path Intellisense — autocompletes file paths.
  • GitLens — see who changed each line and when.
  • indent-rainbow — colour-codes indentation.

Git & GitHub

Git tracks changes to your files. GitHub hosts Git repositories online.

First-time setup

git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main

The daily workflow

git status                     # what's changed?
git diff                       # what exactly changed?
git add .                      # stage everything
git add file.js                # stage one file
git commit -m "Add login form" # save a snapshot
git log --oneline              # see history
git push                       # upload to GitHub

Working with branches

git branch                     # list branches
git checkout -b feature/login  # create and switch
git switch main                # modern: switch branches
git merge feature/login        # merge into current branch
git branch -d feature/login    # delete a merged branch

Undoing things

git restore file.js            # discard unstaged changes
git restore --staged file.js   # unstage, keep changes
git commit --amend             # fix the last commit message
git reset --soft HEAD~1        # undo last commit, keep changes staged
git reset --hard HEAD~1        # undo last commit, DESTROY changes
git revert <commit>            # safe undo: creates a new commit

Remotes

git remote add origin https://github.com/user/repo.git
git remote -v
git clone https://github.com/user/repo.git
git pull                       # fetch + merge
git fetch                      # download without merging

Merge conflicts

<<<<<<< HEAD
your version
=======
their version
>>>>>>> other-branch

Edit the file to keep what you want, delete the markers, then git add and git commit.

A good .gitignore

node_modules/
dist/
build/
.env
.DS_Store
*.log
.vscode/

Never commit secrets API keys, tokens, and .env files. Once pushed, they're in the history forever — rotate the key immediately if it happens.

Node & npm

Node.js lets you run JavaScript outside the browser. npm installs JavaScript packages.

node --version
npm --version

npm init -y                    # create package.json
npm install express            # install a dependency
npm install --save-dev jest    # install a dev dependency
npm install -g some-cli        # install globally
npm uninstall express
npm update
npm run <script-name>

package.json

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest"
  },
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "vitest": "^1.0.0"
  }
}

Version ranges

RangeMeaning
1.2.3Exactly 1.2.3
^1.2.3Any 1.x.x ≥ 1.2.3
~1.2.3Any 1.2.x ≥ 1.2.3
>=1.2.3Anything at least 1.2.3
*Anything

package-lock.json pins the exact versions actually installed. Commit it. node_modules/ — never commit it.

Build Tools

ToolRole
BundlerCombines many files into a few. Webpack, Rollup, esbuild, Vite.
TranspilerConverts modern syntax to older syntax. Babel, SWC, esbuild.
Dev serverServes your app locally with hot reload. Vite, webpack-dev-server.
Task runnerAutomates repeated jobs. npm scripts, Gulp.

Vite — the modern default

npm create vite@latest my-app
cd my-app
npm install
npm run dev        # dev server with instant hot reload
npm run build      # production build → dist/
npm run preview    # serve the production build locally

Vite uses native ES modules during development, so there's almost no startup delay. For production it bundles with Rollup.

You don't need a build tool to start Write plain HTML, CSS, and JS files and open them in a browser. Add tooling when the friction of not having it exceeds the friction of learning it.

Linters & Formatters

A linter finds likely bugs and bad patterns. A formatter rewrites code to a consistent style so you never argue about it.

Prettier

npm install --save-dev prettier

npx prettier --write .
npx prettier --check .
.prettierrc
{
  "semi": true,
  "singleQuote": false,
  "printWidth": 80,
  "tabWidth": 2,
  "trailingComma": "es5"
}

ESLint

npm install --save-dev eslint
npx eslint --init
npx eslint .
eslint.config.js
export default [
  {
    rules: {
      "no-unused-vars": "warn",
      "no-undef": "error",
      "eqeqeq": ["error", "always"],
      "prefer-const": "warn",
    },
  },
];

Testing

TypeTestsTools
UnitOne function or module in isolationVitest, Jest
IntegrationSeveral units working togetherVitest, Jest, Testing Library
End-to-endA real browser clicking through real flowsPlaywright, Cypress
Visual regressionScreenshots compared against a baselinePlaywright, Percy

A Vitest example

// math.js
export function add(a, b) { return a + b; }

// math.test.js
import { describe, it, expect } from "vitest";
import { add } from "./math.js";

describe("add", () => {
  it("adds two positive numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  it("handles negatives", () => {
    expect(add(-1, -1)).toBe(-2);
  });
});

A Playwright example

import { test, expect } from "@playwright/test";

test("homepage shows the title", async ({ page }) => {
  await page.goto("http://localhost:5173");
  await expect(page.locator("h1")).toHaveText("Hello, world!");
});

The testing pyramid

      ╱╲        few, slow, expensive
     ╱E2E╲
    ╱─────╲
   ╱Integr.╲    some
  ╱─────────╲
 ╱   Unit    ╲  many, fast, cheap
╱─────────────╲

Deployment

HostBest forNotes
GitHub PagesStatic sitesFree, tied to your repo
NetlifyStatic + serverless functionsGenerous free tier, drag-and-drop deploys
VercelStatic + serverless, Next.jsExcellent DX
Cloudflare PagesStatic + edge functionsVery fast global network
Render / Railway / Fly.ioFull servers and databasesGood free/cheap tiers
AWS / GCP / AzureEverythingPowerful, complex, easy to overspend

Deploying to GitHub Pages

# 1. Push your repo to GitHub
git add .
git commit -m "Ready to deploy"
git push

# 2. On GitHub: Settings → Pages → Source: main branch / root
# 3. Your site appears at https://username.github.io/repo-name/

Environment variables

# .env  (never commit this)
API_KEY=abc123
DATABASE_URL=postgres://…

# Load in Node
process.env.API_KEY

# Vite exposes only variables prefixed with VITE_
# (so you don't accidentally leak secrets to the client)
import.meta.env.VITE_PUBLIC_URL

Anything shipped to the browser is public Environment variables in a front-end build are compiled into the bundle. They are not secret. Only the server can keep secrets.

Part VII — Backend Basics

Servers & Node

A server listens for HTTP requests and sends responses. It can serve static files, run business logic, query a database, and render HTML.

A minimal Node server

server.js
import http from "node:http";

const server = http.createServer((req, res) => {
  if (req.url === "/api/hello") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: "Hello from the server" }));
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Not found");
  }
});

server.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

The same thing with Express

import express from "express";
const app = express();

app.use(express.json());          // parse JSON request bodies

app.get("/api/hello", (req, res) => {
  res.json({ message: "Hello from the server" });
});

app.post("/api/users", (req, res) => {
  const { name } = req.body;
  if (!name) return res.status(400).json({ error: "Name required" });
  res.status(201).json({ id: 1, name });
});

app.listen(3000, () => console.log("http://localhost:3000"));

Middleware

Middleware functions run in order between the request and your route handler. They can read the request, modify it, end it, or pass it on.

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();                       // pass control onward
});

// Auth middleware
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  req.user = verifyToken(token);
  next();
}

app.get("/api/profile", requireAuth, (req, res) => {
  res.json(req.user);
});

REST APIs

REST is a convention for structuring an API around resources and HTTP methods.

Method + pathMeans
GET /postsList all posts
GET /posts/42Get post 42
POST /postsCreate a post
PUT /posts/42Replace post 42
PATCH /posts/42Update part of post 42
DELETE /posts/42Delete post 42

Conventions

  • Use nouns for paths, not verbs: /posts, not /getPosts.
  • Pluralise collections: /users, /users/42.
  • Use HTTP status codes properly.
  • Return JSON with a consistent shape.
  • Version the API: /api/v1/users.
  • Paginate large collections: ?page=2&limit=20.

GraphQL in one paragraph

An alternative to REST where the client specifies exactly what fields it wants in a single request. One endpoint, a typed schema, no over-fetching. More powerful, more setup.

Databases

SQL (relational)NoSQL (document)
StructureTables, rows, columnsCollections of JSON-like documents
SchemaStrict, defined up frontFlexible
RelationshipsForeign keys and JOINsEmbedding or references
Query languageSQLVaries (MongoDB uses its own)
ExamplesPostgreSQL, MySQL, SQLiteMongoDB, Firestore, DynamoDB
Good forStructured data, complex relationships, transactionsRapid iteration, varied shapes, horizontal scale

SQL essentials

-- Create
CREATE TABLE users (
  id       SERIAL PRIMARY KEY,
  name     TEXT NOT NULL,
  email    TEXT UNIQUE NOT NULL,
  created  TIMESTAMP DEFAULT NOW()
);

-- Insert
INSERT INTO users (name, email) VALUES ('Ada', 'ada@example.com');

-- Read
SELECT id, name FROM users WHERE email LIKE '%@example.com' ORDER BY name LIMIT 10;

-- Update
UPDATE users SET name = 'Ada Lovelace' WHERE id = 1;

-- Delete
DELETE FROM users WHERE id = 1;

-- Join
SELECT posts.title, users.name
FROM posts
JOIN users ON posts.user_id = users.id
WHERE users.id = 1;

Always parameterise queries String-concatenating user input into SQL is how SQL injection happens. Use placeholders.

// ✗ dangerous
db.query(`SELECT * FROM users WHERE email = '${email}'`);

// ✓ safe
db.query("SELECT * FROM users WHERE email = $1", [email]);

Authentication

ApproachHow it works
Session cookiesServer stores a session; browser holds a cookie ID. Server-rendered apps.
JWTServer signs a token containing claims; client sends it in the Authorization header. Stateless.
OAuth 2.0"Sign in with Google." You delegate auth to a third party.
Magic linkEmail a one-time login link. No password.
Passkeys / WebAuthnBiometric or hardware-key login. Phishing-resistant.

Password storage — the only rule

// NEVER store plain passwords. Hash with a slow algorithm.
import bcrypt from "bcrypt";

const hash = await bcrypt.hash(password, 12);
const ok = await bcrypt.compare(attempt, hash);

JWT in practice

// Server: issue
const token = jwt.sign({ userId: 42 }, SECRET, { expiresIn: "1h" });

// Client: send
fetch("/api/profile", {
  headers: { Authorization: `Bearer ${token}` }
});

// Server: verify
const payload = jwt.verify(token, SECRET);

Security checklist

  • Hash passwords with bcrypt, scrypt, or Argon2. Never MD5 or SHA-1.
  • Serve everything over HTTPS.
  • Set HttpOnly, Secure, SameSite on cookies.
  • Validate and sanitise all input on the server. Client validation is UX, not security.
  • Parameterise every database query.
  • Escape output to prevent XSS.
  • Use CSRF tokens for cookie-based state changes.
  • Rate-limit login and password-reset endpoints.
  • Keep dependencies updated — npm audit.
  • Set a Content-Security-Policy header.

Part VIII — Putting It Together

Your First Project — A Working Counter

Three files in one folder. No tools, no build step. Open index.html in a browser.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Counter</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main class="card">
    <h1>Counter</h1>
    <p id="count" aria-live="polite">0</p>
    <div class="buttons">
      <button id="decrement">−1</button>
      <button id="reset">Reset</button>
      <button id="increment">+1</button>
    </div>
  </main>
  <script src="app.js" defer></script>
</body>
</html>
styles.css
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: system-ui, sans-serif;
  background: #fafafa;
}

.card {
  border: 2px solid #000;
  background: #fff;
  padding: 2rem 2.5rem;
  text-align: center;
  min-width: 280px;
}

h1 {
  margin: 0 0 .5rem;
  font-size: 1.1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

#count {
  font-size: 3.5rem;
  margin: .5rem 0 1.5rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.buttons {
  display: flex;
  gap: .5rem;
  justify-content: center;
}

button {
  font: inherit;
  font-weight: 600;
  padding: .55rem 1rem;
  border: 2px solid #000;
  background: #fff;
  cursor: pointer;
  transition: background-color .15s, color .15s;
}

button:hover {
  background: #000;
  color: #fff;
}

button:focus-visible {
  outline: 3px solid #000;
  outline-offset: 2px;
}
app.js
let count = 0;

const display   = document.querySelector("#count");
const increment = document.querySelector("#increment");
const decrement = document.querySelector("#decrement");
const reset     = document.querySelector("#reset");

function render() {
  display.textContent = count;
  display.style.color = count < 0 ? "#b00" : "#000";
}

increment.addEventListener("click", () => { count += 1; render(); });
decrement.addEventListener("click", () => { count -= 1; render(); });
reset.addEventListener("click", () => { count = 0; render(); });

// Bonus: keyboard shortcuts
document.addEventListener("keydown", (e) => {
  if (e.key === "+" || e.key === "ArrowUp")   { count += 1; render(); }
  if (e.key === "-" || e.key === "ArrowDown") { count -= 1; render(); }
  if (e.key === "0")                          { count = 0;  render(); }
});

render();

What you just used

  • Semantic HTML with a <main> landmark
  • aria-live so screen readers announce the count changing
  • CSS Grid for centering, Flexbox for the button row
  • box-sizing: border-box, custom hover and focus states
  • JavaScript closures, event listeners, DOM manipulation
  • defer so the script runs after the DOM is ready

Common Errors Decoded

MessageWhat it meansFix
Cannot read properties of null (reading 'x') Your selector matched nothing. Check spelling and case; make sure the script runs after the element exists.
X is not defined You used a variable that doesn't exist. Typo, or a missing const/let.
X is not a function You called something that isn't callable. Typo in a method name, or the script loaded twice.
Cannot set properties of undefined Chained access past a missing level. Use optional chaining a?.b?.c or guard with an if.
Uncaught (in promise) … An async operation failed unhandled. Add try/catch around await.
Blocked by CORS policy The other origin didn't allow your site. Fix it server-side with an Access-Control-Allow-Origin header — or proxy through your own backend.
404 Not Found The path is wrong. Check the file location and case. Servers are case-sensitive.
Mixed Content An HTTPS page tried to load an HTTP resource. Change the URL to https://.
Maximum call stack size exceeded Infinite recursion. Find the function calling itself with no exit condition.
CSS not applying Selector mismatch, missing link, or override. Check the Network tab, then check specificity in the Styles panel.
Layout jumps after load Images without dimensions. Add width and height attributes to every image.
Everything is 0px tall Floated or absolutely positioned children don't contribute height. Use Flexbox or Grid instead, or add a clearfix.

Debugging routine

  1. Read the error message properly. It usually names the file and line.
  2. Open DevTools → Console. Fix the first error, not the tenth.
  3. console.log() the value you're unsure about.
  4. Set a breakpoint and step through.
  5. Change one thing at a time.
  6. If stuck for 30 minutes, explain the problem out loud. You'll often solve it.

Glossary A — Acronyms & Abbreviations

TermStands forIn plain English
a11yaccessibilityNumeronym: a + 11 letters + y. Making sites usable by everyone.
AJAXAsynchronous JavaScript and XMLLoading data in the background without a full page reload.
APIApplication Programming InterfaceA contract for how one program asks another for things.
ARIAAccessible Rich Internet ApplicationsHTML attributes that describe UI to screen readers.
ASTAbstract Syntax TreeA tree representation of code, used by linters and compilers.
BEMBlock, Element, ModifierA CSS class naming convention: .card__title--large.
CDNContent Delivery NetworkA global server network that serves files from wherever is closest.
CI/CDContinuous Integration / Continuous DeploymentAutomatically testing and shipping code when you push.
CLICommand Line InterfaceControlling a program by typing commands in a terminal.
CMSContent Management SystemEditing site content without touching code (WordPress, etc.).
CORSCross-Origin Resource SharingThe browser rule blocking a page from calling a different domain unless allowed.
CRUDCreate, Read, Update, DeleteThe four basic operations on data.
CSRFCross-Site Request ForgeryAn attack that tricks a logged-in user's browser into making a request.
CSSCascading Style SheetsThe language that controls how HTML looks.
CSSOMCSS Object ModelThe parsed, in-memory representation of your stylesheets.
CSRClient-Side RenderingThe browser builds the page with JavaScript.
CTACall To ActionThe button you want the user to press.
DOMDocument Object ModelThe live tree of objects the browser builds from your HTML.
DNSDomain Name SystemThe internet's phone book: names to IP addresses.
DXDeveloper ExperienceHow pleasant a tool is to work with.
ES6ECMAScript 6 (2015)The big JavaScript update that added classes, arrows, promises, modules.
FOUCFlash Of Unstyled ContentThe brief moment a page shows unstyled HTML before CSS loads.
FTPFile Transfer ProtocolAn older way to move files to a server.
GUIGraphical User InterfacePoint-and-click interface, as opposed to a CLI.
HTMLHyperText Markup LanguageThe language that gives a page its structure.
HTTP / HTTPSHyperText Transfer Protocol (Secure)The rules browsers and servers use. The S means encrypted.
i18ninternationalizationNumeronym. Making software work in many languages.
IDEIntegrated Development EnvironmentA code editor with a debugger, terminal, and more built in.
IIFEImmediately Invoked Function Expression(function(){ … })() — runs the moment it's defined.
INPInteraction to Next PaintA Core Web Vital measuring responsiveness.
IPInternet ProtocolAddressing and routing packets between machines.
JSONJavaScript Object NotationA text format for structured data; the lingua franca of APIs.
JS / JSXJavaScript / JavaScript XMLJSX lets you write HTML-like markup inside JavaScript (React).
JWTJSON Web TokenA signed token used to prove who a user is.
k8sKubernetesNumeronym. Container orchestration.
LAMPLinux, Apache, MySQL, PHPA classic all-in-one back-end stack.
LCPLargest Contentful PaintA Core Web Vital measuring when main content appears.
MIMEMultipurpose Internet Mail ExtensionsThe type label for content: text/html, application/json.
MVCModel–View–ControllerA pattern organising code into data, display, and logic.
npmNode Package ManagerThe tool and registry for installing JavaScript libraries.
ORMObject–Relational MapperCode that translates between objects and database tables.
PWAProgressive Web AppA website that can be installed and work offline like a native app.
RESTRepresentational State TransferAn API style built around URLs and HTTP methods.
SEOSearch Engine OptimizationMaking a site easier for search engines to find and rank.
SPASingle Page ApplicationOne HTML page where JavaScript swaps content instead of reloading.
SQLStructured Query LanguageThe language for querying relational databases.
SSG / SSRStatic Site Generation / Server-Side RenderingBuilding HTML ahead of time / building it per request on the server.
SSL / TLSSecure Sockets Layer / Transport Layer SecurityThe encryption behind HTTPS. TLS is the modern version.
TCPTransmission Control ProtocolReliable, ordered delivery of packets.
UI / UXUser Interface / User ExperienceWhat it looks like / how it feels to use.
URI / URLUniform Resource Identifier / LocatorThe address of a resource on the web.
UUIDUniversally Unique IdentifierA long random ID like f47ac10b-58cc-….
VCSVersion Control SystemSoftware that tracks file changes over time. Git is one.
VDOMVirtual DOMAn in-memory copy of the DOM that frameworks diff against. Used by React.
WASMWebAssemblyA fast binary format letting other languages run in the browser.
WCAGWeb Content Accessibility GuidelinesThe standard for making sites usable by people with disabilities.
XSSCross-Site ScriptingInjecting malicious script into a page through user input.
XMLeXtensible Markup LanguageA general markup format; largely replaced by JSON for APIs.

Numeronyms — words shortened with numbers

Developers compress long words by keeping the first and last letter and putting the count of the missing letters in between.

  • a11y — accessibility
  • i18n — internationalization
  • l10n — localization
  • k8s — Kubernetes
  • o11y — observability
  • p13n — personalization
  • c14n — canonicalization
  • s12n — serialization

Glossary B — Jargon & Idioms

Core concepts

Boilerplate
Standard code you copy into every project before real work begins. "Start from the boilerplate."
Vanilla JS
Plain JavaScript with no frameworks or libraries. "I built it in vanilla JS."
Syntactic sugar
Syntax that makes code nicer to read without adding new capability. Arrow functions and template literals are sugar.
Polyfill
Code that adds a modern feature to older browsers that don't support it.
Shim
Similar to a polyfill, but intercepts and corrects existing behaviour rather than adding a missing one.
Transpiler
A tool that converts one language version into another — TypeScript into JavaScript, or modern JS into older JS.
Bundler
A tool that combines many files into a few for the browser. Webpack, Rollup, esbuild, Vite.
Linter
A tool that reads your code and flags likely mistakes and style violations. ESLint is the standard for JavaScript.
Minify
Strip whitespace and shorten names to shrink file size. Makes code unreadable to humans, which is fine for production.
Uglify
An older minifier. The verb "uglify" means the same as minify.
Tree shaking
Removing code from your bundle that nothing actually imports.
Code splitting
Splitting a bundle into chunks that load on demand, so the first page is smaller.
Hot reload
The dev server updates the page the instant you save a file, without losing state.
HMR
Hot Module Replacement — hot reload at the module level.
Scaffolding
Generating a project's folder structure and starter files automatically.
Closure
A function that remembers the variables from where it was defined, even after that scope has finished running.
Hoisting
JavaScript's behaviour of moving declarations to the top of their scope before running the code.
Scope
Where a variable is visible. const and let are block-scoped; var is function-scoped.
Lexical scope
Scope determined by where code is written, not where it's called.
Callback
A function you pass to another function to be run later.
Callback hell
Deeply nested callbacks that are hard to read. Solved by promises and async/await.
Promise / async-await
Two ways to handle operations that take time without freezing the page.
Event listener
Code that waits for something to happen — a click, a keypress, a scroll — then runs.
Event bubbling
An event fires on the target, then on each ancestor in turn, up to the document.
Event capturing
The opposite phase: the event travels down from the document to the target first.
Debounce
Delay running a function until the user stops doing something. Used for search-as-you-type.
Throttle
Run a function at most once every N milliseconds. Used for scroll handlers.
Breakpoint
The screen width at which a media query kicks in and your layout changes.
Viewport
The visible area of the page in the browser window.
Semantic HTML
Using tags that describe their content (<nav>, <article>) instead of generic <div>s.
Progressive enhancement
Build a working baseline first, then layer on enhancements for capable browsers.
Graceful degradation
The opposite: build the full experience, then make sure it degrades acceptably on older browsers.
Mobile-first
Write base styles for small screens, then add complexity with min-width media queries.
FOUC
Flash Of Unstyled Content — the ugly moment before CSS loads.
Hydration
Attaching JavaScript behaviour to server-rendered HTML in the browser.
Idempotent
Doing it twice has the same result as doing it once. GET and DELETE should be idempotent.
Pure function
A function with no side effects: same input, same output, every time.
Side effect
Anything a function does beyond returning a value — writing to the DOM, making a request, logging.
Immutable
Cannot be changed after creation. React code often treats data as immutable.
Memoization
Caching the result of an expensive function so repeat calls are instant.
Recursion
A function that calls itself. Always needs a base case.
Higher-order function
A function that takes a function as an argument or returns one. map, filter, reduce are all examples.

Workflow & team slang

Repo
Short for repository — a project folder tracked by Git.
Commit
A saved snapshot of your changes with a message explaining them.
Branch
A parallel copy of the code where you can work without affecting main.
Fork
Your own copy of someone else's repository, usually on GitHub.
Clone
Downloading a repository to your machine.
PR / Pull Request
A request to merge your branch into the main one, usually with review and discussion.
MR
Merge Request — GitLab's name for a pull request.
Diff
A line-by-line comparison of two versions of a file.
Merge conflict
Git can't automatically combine two sets of changes to the same lines. You resolve it by hand.
Rebase
Replaying your commits on top of another branch, producing a linear history.
Cherry-pick
Copying a single commit from one branch onto another.
Stash
Temporarily shelving uncommitted changes so you can switch tasks.
HEAD
The commit you're currently on.
Origin
The default name for the remote repository you cloned from.
Upstream
The original repository a fork came from.
Issue
A tracked task, bug report, or feature request in a project.
Backlog
The list of work not yet started.
Sprint
A fixed time period (usually 1–2 weeks) in which a set of work is completed.
Standup
A short daily meeting where each person says what they did, what they'll do, and what's blocking them.
Ship it
Deploy it to real users. "Looks good — ship it."
Prod / staging / dev
The live environment / a realistic copy for testing / your local machine.
Deploy
Publishing your code so it's reachable on the internet.
Rollback
Reverting a deployment to the previous version.
Hotfix
An urgent fix pushed straight to production.
Feature flag
A switch that turns a feature on or off without deploying new code.
Canary release
Rolling a change out to a small percentage of users first.
Environment variable
A value stored outside your code (like an API key) so it isn't committed to Git.
Seed data
Fake or initial data loaded into a database for development.
Dogfooding
Using your own product internally to find problems before users do.
Rubber duck debugging
Explaining your bug out loud to an inanimate object. You usually find the answer mid-sentence.
Yak shaving
Being pulled into a chain of unrelated tasks before you can do the thing you set out to do.
Bikeshedding
Spending disproportionate time arguing about something trivial while the hard problems go unaddressed.
Spaghetti code
Tangled, hard-to-follow code with no clear structure.
Lasagna code
The opposite problem: so many layers of abstraction that nothing is traceable.
Copy-pasta
Code copied from somewhere else without understanding it.
Code smell
A pattern that isn't a bug but suggests deeper problems.
Technical debt
Shortcuts taken now that will cost more effort to fix later.
Refactoring
Improving the structure of code without changing what it does.
Magic number
A hard-coded value with no explanation. "Why 47? Name it."
Magic string
The same, but for text.
Hard-coded
Baked directly into the code rather than configurable.
DRY
Don't Repeat Yourself — factor out duplication.
WET
Write Everything Twice — the anti-pattern DRY warns against.
KISS
Keep It Simple, Stupid — the simplest solution is usually right.
YAGNI
You Aren't Gonna Need It — don't build for imagined future requirements.
SOLID
Five principles of object-oriented design. Useful to know the name of.
foo / bar / baz
Conventional placeholder names used in examples when the actual name doesn't matter.
Lorem ipsum
Filler Latin text used to preview layouts before real copy exists.
Lorem ipsum dolor
The opening words of the standard filler passage.
Sandbox
An isolated environment where it's safe to experiment or run untrusted code.
Cache
A stored copy of something so it can be reused faster. "Have you tried clearing your cache?" is the oldest joke in the industry.
Cache busting
Adding a hash to a filename (app.a3f9c.js) so browsers fetch the new version.
CDN edge
A server in a CDN's network close to the user.
Endpoint
A specific URL an API exposes. GET /users/1
Payload
The actual data in a request or response, as opposed to the headers.
Query string
The part of a URL after the ?, carrying parameters.
Rate limit
A cap on how many requests you can make in a time window.
Throttling (API)
Deliberately slowing or limiting requests.
Webhook
A reverse API call: the server notifies your app when something happens.
Polling
Repeatedly asking the server for updates on a timer.
WebSocket
A persistent two-way connection, for chat and live updates.
SSE
Server-Sent Events — one-way streaming from server to browser.

Quick Cheat Sheet

HTML boilerplate

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Title</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main></main>
  <script src="app.js" defer></script>
</body>
</html>

CSS reset starter

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

body {
  min-height: 100vh;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

p, h1, h2, h3, h4, h5, h6 {
  overflow-wrap: break-word;
}

Centering, all the ways

/* Flexbox */
.parent { display: flex; justify-content: center; align-items: center; }

/* Grid */
.parent { display: grid; place-items: center; }

/* Absolutely positioned */
.child {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
}

/* Horizontal centering of a block */
.child { margin-inline: auto; width: fit-content; }

JavaScript DOM snippets

// Select
const el = document.querySelector(".card");

// Create
const li = document.createElement("li");
li.textContent = "New";
document.querySelector("ul").append(li);

// Event
el.addEventListener("click", (e) => { … });

// Toggle a class
el.classList.toggle("active");

// Store
localStorage.setItem("key", JSON.stringify(data));
const data = JSON.parse(localStorage.getItem("key"));

// Fetch
const res = await fetch("/api/items");
const items = await res.json();

Git daily commands

git status
git add .
git commit -m "message"
git push
git pull
git checkout -b new-branch
git log --oneline

Terminal basics

pwd                 # where am I
ls -la              # list all files
cd folder           # go into folder
cd ..               # go up
mkdir name          # make a folder
rm file             # delete a file (rm -rf folder for folders)
cp a b              # copy
mv a b              # move / rename
cat file            # print a file
touch file          # create an empty file

Learning Path

Phase 1 — The basics (2–4 weeks)

  • HTML: structure, semantic elements, forms, accessibility.
  • CSS: selectors, box model, Flexbox, Grid, responsive design.
  • JavaScript: variables, functions, arrays, objects, DOM, events.
  • Build: a personal page, a to-do list, a calculator, a quiz.

Phase 2 — Working with data (2–4 weeks)

  • Async JavaScript, fetch, promises, error handling.
  • Git and GitHub properly: branches, PRs, conflicts.
  • DevTools in depth: Network, Sources, Application panels.
  • Build: a weather app, a movie search, a GitHub profile viewer.

Phase 3 — Tools and structure (2–4 weeks)

  • npm, package.json, Vite.
  • Modules, ES6+, classes.
  • Linting and formatting.
  • Deployment to Netlify or GitHub Pages.
  • Build: a multi-page site, a markdown blog, a small game.

Phase 4 — Pick a direction

  • Front-end path: React or Vue, state management, component design, accessibility in depth.
  • Back-end path: Node + Express, databases, auth, REST design, deployment.
  • Full-stack path: both, plus an ORM and a hosting platform.

Phase 5 — Go deeper

  • Testing: unit, integration, end-to-end.
  • Performance: Core Web Vitals, profiling, optimisation.
  • TypeScript.
  • System design basics: caching, queues, scaling, observability.

The one rule that matters Build things. Tutorials feel productive but the learning happens when you're stuck on your own project with no instructions and you have to figure it out. Pick something small and slightly too hard.

Trusted Resources

ResourceWhat it is
MDN Web Docs
developer.mozilla.org
The definitive reference for HTML, CSS, and JavaScript. If you read one site, read this one.
web.devGoogle's guides on performance, accessibility, and best practice.
Can I Use
caniuse.com
Browser support tables for every web feature.
JavaScript.infoA thorough, modern JavaScript tutorial.
The Odin ProjectA free, structured full-stack curriculum.
freeCodeCampFree interactive courses and certifications.
CSS-TricksDeep dives on CSS, especially Flexbox and Grid.
Frontend MentorReal design files to build from — great for practice.
Smashing MagazineLong-form articles on front-end and design.
Stack OverflowQ&A. Search before asking; read the accepted answer's comments.
GitHubRead other people's code. It's the best teacher there is.
W3C Validator
validator.w3.org
Checks your HTML for errors.

A note on tutorials Watching someone build something feels like progress. It isn't. Type the code yourself, then close the tutorial and rebuild it from memory. Then change something the tutorial didn't cover. That last step is where the learning actually happens.

↑
← Back Web Development ⌂ Encyclopedia Next →