school Interactive Learning

Learn to Build
|

An interactive tutorial covering the three pillars of web development - HTML structure, CSS styling, and JavaScript interactivity - with 14 live demos, cheatsheets, and a scored quiz.

expand_more
14Live Demos
3Core Topics
15Quiz Questions
3Cheatsheets

Explore the Tutorial

Three comprehensive sections - each packed with explanations, annotated code, and hands-on demos you can experiment with directly in your browser.

flag

Learning Objectives

By the end of this section you will be able to:

  • Explain what HTML is and why it is the foundation of every web page.
  • Write a valid HTML5 document from scratch with proper meta tags.
  • Distinguish between block-level and inline elements and know when to use each.
  • Use attributes correctly - including global, boolean, and element-specific attributes.
  • Use semantic elements to improve accessibility, SEO, and code readability.
  • Build a multi-section page with forms, tables, links, images, and multimedia.
description

What is HTML?

HTML

HTML (HyperText Markup Language) is the standard language for creating and structuring content on the web. Think of it as the skeleton of a web page - it defines what content exists (headings, paragraphs, images, links) while CSS and JavaScript control how that content looks and behaves.

Why does this matter? Without well-structured HTML, search engines cannot index your content correctly, screen readers cannot navigate it, and browsers may render it unpredictably. Writing clean, semantic HTML is the single most important best practice in front-end development - it makes your site accessible to everyone, including users who rely on assistive technologies.

  • HTML uses tags (e.g., <p>, <h1>) to wrap content and give it meaning.
  • Tags are paired - an opening tag (<p>) and a closing tag (</p>) - with content in between.
  • Some tags are self-closing (e.g., <img>, <br>) because they have no child content.
  • The browser reads HTML top-to-bottom and builds a tree structure called the DOM (Document Object Model).
title

Headings & Paragraphs

HTML

Headings (<h1> to <h6>) and paragraphs (<p>) are the most fundamental HTML elements. Headings define a hierarchy of importance: <h1> is the main page title (use only one per page), <h2> marks major sections, <h3> sub-sections, and so on. Search engines use this hierarchy to understand your content structure, and screen readers let users navigate by heading level - so getting the hierarchy right matters for both SEO and accessibility.

Paragraphs are block-level elements that automatically add spacing above and below. The browser collapses multiple spaces and line breaks inside a <p> into a single space - if you need to preserve formatting, use <pre> (preformatted text) instead. For a line break without starting a new paragraph, use the self-closing <br> tag. For a thematic break (horizontal rule), use <hr>.

<!-- Heading hierarchy  -  use in order, never skip levels -->
<h1>Main Page Title</h1>        <!-- Only ONE h1 per page -->
<h2>Section Heading</h2>
<h3>Sub-section Heading</h3>
<h4>Detail Heading</h4>

<!-- Paragraphs add automatic spacing -->
<p>This is a paragraph. The browser adds margin
   above and below automatically.</p>
<p>This is another paragraph.     Extra spaces
   and line breaks     are collapsed into one.</p>

<!-- Preformatted text preserves whitespace exactly -->
<pre>
  This   text   preserves
  all    spaces  and
  line breaks exactly as written.
</pre>

<!-- Line break and horizontal rule -->
<p>Line one.<br>Line two (same paragraph).</p>
<hr> <!-- Thematic break / horizontal rule -->
science Interactive: Heading Hierarchy Preview

Select a heading level and type your text. Click Add to build a heading hierarchy. Notice how each level gets progressively smaller - this visual hierarchy matches the semantic importance.

Add headings above to build a hierarchy…
format_bold

Text Formatting & Comments

HTML

HTML provides elements for giving text semantic meaning beyond just visual appearance. <strong> marks text as important (browsers render it bold, but screen readers also emphasise it). <em> marks text as emphasised (rendered italic, but with vocal stress in screen readers). These are different from <b> (bold) and <i> (italic), which are purely visual with no semantic meaning.

HTML comments (<!-- ... -->) are invisible to users but visible in the source code. They are essential for documenting your code, temporarily disabling elements during debugging, and leaving notes for other developers (or your future self).

<!-- ===== SEMANTIC formatting (preferred) ===== -->
<p><strong>Important:</strong> Submit your assignment by Friday.</p>
<p>Read the <em>entire</em> brief before starting.</p>

<!-- ===== Additional formatting elements ===== -->
<p><mark>Highlighted text</mark> draws attention.</p>
<p><del>Deleted text</del> and <ins>inserted text</ins>.</p>
<p>H<sub>2</sub>O is water. E = mc<sup>2</sup>.</p>
<p><code>console.log()</code> is an inline code snippet.</p>

<!-- ===== VISUAL-ONLY formatting (use sparingly) ===== -->
<p><b>Bold (no semantic meaning)</b></p>
<p><i>Italic (no semantic meaning)</i></p>

<!-- This is an HTML comment  -  invisible to users -->
<!-- TODO: Add the contact form here -->
<!-- <p>This paragraph is commented out / disabled.</p> -->
science Interactive: Text Formatting Preview

Type text and click a formatting tag to see it rendered and view the generated HTML code.

format_list_bulleted

Lists - Ordered, Unordered & Description

HTML

HTML provides three types of lists. Unordered lists (<ul>) display bullet points - use them when the order doesn't matter. Ordered lists (<ol>) display numbered items - use them for step-by-step instructions, rankings, or sequences. Description lists (<dl>) pair terms with their definitions - use them for glossaries, FAQs, or metadata. Lists can also be nested (a list inside another list) to create multi-level hierarchies.

<!-- Unordered list (bullet points) -->
<ul>
  <li>HTML  -  structure</li>
  <li>CSS  -  style</li>
  <li>JavaScript  -  interactivity</li>
</ul>

<!-- Ordered list (numbered) -->
<ol>
  <li>Plan your layout</li>
  <li>Write the HTML structure</li>
  <li>Add CSS styling</li>
  <li>Add JavaScript interactivity</li>
</ol>

<!-- Nested list -->
<ul>
  <li>Front-end
    <ul>
      <li>HTML</li>
      <li>CSS</li>
      <li>JavaScript</li>
    </ul>
  </li>
  <li>Back-end
    <ul>
      <li>Node.js</li>
      <li>Python</li>
    </ul>
  </li>
</ul>

<!-- Description list (term + definition pairs) -->
<dl>
  <dt>HTML</dt>
  <dd>HyperText Markup Language  -  structures web content.</dd>
  <dt>CSS</dt>
  <dd>Cascading Style Sheets  -  controls visual presentation.</dd>
</dl>
science Interactive: List Type Comparison

Click a list type to see it rendered and view the HTML code.

code_blocks

HTML Entities & Special Characters

HTML

Some characters have special meaning in HTML - for example, < starts a tag, > ends one, and & begins an entity reference. To display these characters as visible text rather than HTML code, you must use character entities. Entities start with & and end with ;. Without them, the browser will try to interpret the characters as HTML, which either breaks your page or creates a W3C validation error.

<!-- Common HTML entities -->
&lt;     →  <    (less than)
&gt;     →  >    (greater than)
&amp;    →  &    (ampersand)
&quot;   →  "    (double quote)
&apos;   →  '    (single quote / apostrophe)
&nbsp;   →       (non-breaking space)
&copy;   →  ©    (copyright)
&trade;  →  ™    (trademark)
&mdash;  →   -     (em dash)

<!-- Why entities matter: displaying code in HTML -->
<p>To write a tag, type &lt;p&gt; in your HTML.</p>
<!-- Without entities, <p> would create a new paragraph! -->

<!-- Non-breaking space prevents word wrapping -->
<p>10&nbsp;kg means the number and unit stay together.</p>
account_tree

Document Structure & Meta Tags

HTML

Every valid HTML5 document starts with a <!DOCTYPE html> declaration, which tells the browser to use the latest rendering mode instead of falling back to legacy "quirks mode". Inside the <head>, meta tags provide critical information: charset sets the character encoding (always use UTF-8), and the viewport meta tag enables responsive design on mobile devices. Omitting these tags is a common beginner mistake that leads to broken layouts on phones and garbled special characters.

<!-- The DOCTYPE is required  -  without it, browsers fall into
     "quirks mode" and rendering becomes unpredictable. -->
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- UTF-8 supports every human language and emoji -->
  <meta charset="UTF-8">

  <!-- This meta tag makes the page responsive on mobile.
       Without it, phones render the page at desktop width
       and force the user to pinch-zoom. -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- The title appears in browser tabs and search-engine results -->
  <title>My First Page</title>

  <!-- External stylesheet link (keeps HTML and CSS separated) -->
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <h1>Hello, World!</h1>
  <p>This is my first web page.</p>

  <!-- Scripts load at the bottom so the HTML renders first -->
  <script src="js/app.js"></script>
</body>
</html>
accessibility_new

Semantic HTML & Why It Matters

Best Practice

HTML5 introduced semantic elements - tags whose names describe the role of their content, not just its appearance. Using <nav> instead of <div class="nav"> tells the browser, screen readers, and search engines that the section is a navigation block. This matters because:

  • Accessibility: Screen readers can jump between <main>, <nav>, and <aside> landmarks, letting visually-impaired users navigate efficiently.
  • SEO: Search engines rank pages higher when they can identify headings, articles, and navigation clearly.
  • Maintainability: When another developer reads <article>, they instantly know the section is a self-contained piece of content - no guessing required.
<!-- BAD: Non-semantic "div soup"  -  hard to read, bad for a11y -->
<div class="header">
  <div class="nav">...</div>
</div>
<div class="main">
  <div class="article">...</div>
</div>

<!-- GOOD: Semantic elements  -  self-documenting and accessible -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h2>Understanding Semantic HTML</h2>
    <p>Using the right element for the job improves
       accessibility, SEO, and developer experience.</p>
    <figure>
      <img src="diagram.png" alt="Semantic element hierarchy">
      <figcaption>Figure 1 – The semantic element tree.</figcaption>
    </figure>
  </article>

  <aside>
    <h3>Related Resources</h3>
    <p>See the MDN Web Docs for a full list of HTML5 elements.</p>
  </aside>
</main>

<footer>
  <p>&copy; 2026 My Website</p>
</footer>
view_stream

Block vs Inline Elements

HTML

Every HTML element has a default display behaviour that determines how it flows within the page. Understanding the difference between block and inline elements is essential - it controls whether elements stack vertically or sit side-by-side, and what CSS properties (like width, height, and margin) you can apply to them.

  • Block elements (<div>, <p>, <h1>–<h6>, <section>, <ul>) start on a new line and stretch to fill the full width of their parent container. You can set width, height, margin, and padding on all four sides.
  • Inline elements (<span>, <a>, <strong>, <em>, <code>, <img>) flow within a line of text and only take up as much width as their content. Vertical margin and explicit width/height have no effect on inline elements.
  • Inline-block combines both: the element flows inline but accepts width, height, and vertical margins - useful for buttons and badges.
<!-- Block elements stack vertically -->
<div class="card">
  <h2>Block-level heading</h2>   <!-- takes full width, new line -->
  <p>Block-level paragraph.</p>   <!-- takes full width, new line -->
</div>

<!-- Inline elements sit side-by-side within text -->
<p>
  Visit <a href="/about">our about page</a> for
  <strong>more info</strong> and <em>examples</em>.
</p>

<!-- Inline-block: flows inline but accepts width/height -->
<span style="display: inline-block; width: 100px; height: 30px;
             background: teal; color: white; text-align: center;
             line-height: 30px; border-radius: 4px;">
  Badge
</span>
science Interactive: Element Flow Playground

Add block, inline, and inline-block elements to see how they flow on the page.

link

Attributes, Links & Images

HTML

HTML attributes provide extra information about elements and are written inside the opening tag. Some attributes are universal (available on every element), while others are specific to certain tags. Getting attributes right is critical - for example, a missing alt attribute on an image means screen readers cannot describe the image to blind users, and a missing href means a link goes nowhere.

  • Global attributes work on any element: id, class, style, title, data-* (custom data), hidden, tabindex.
  • Boolean attributes (required, disabled, checked, hidden) are true just by being present - they don't need a value.
  • <a href> creates hyperlinks. Use target="_blank" with rel="noopener noreferrer" for security when opening external links in new tabs.
  • <img> always needs src (the file path) and alt (a description). For responsive images, add loading="lazy" and use the srcset attribute to serve different sizes.
<!-- Global attributes: id, class, data-*, title -->
<section id="features" class="container" data-section="2"
         title="Product features">
  <h2>Features</h2>
</section>

<!-- Links: internal, external, and anchor -->
<a href="/about">Internal link (same site)</a>

<!-- External link: opens in new tab securely -->
<a href="https://developer.mozilla.org"
   target="_blank"
   rel="noopener noreferrer">
  MDN Web Docs ↗
</a>

<!-- Anchor link: scrolls to the element with id="features" -->
<a href="#features">Jump to Features</a>

<!-- Images: always include meaningful alt text -->
<img src="images/hero.jpg"
     alt="Developer working at a laptop in a modern office"
     width="800" height="450"
     loading="lazy">

<!-- Responsive image: serve different files by screen size -->
<img srcset="images/hero-480.jpg 480w,
             images/hero-800.jpg 800w,
             images/hero-1200.jpg 1200w"
     sizes="(max-width: 600px) 480px,
            (max-width: 1000px) 800px,
            1200px"
     src="images/hero-800.jpg"
     alt="Responsive hero image example">

<!-- Boolean attributes: just present = true -->
<input type="email" required disabled>
<!-- ↑ required and disabled have no ="value" needed -->
table_chart

Forms, Tables & Multimedia

HTML

Beyond basic text and images, HTML provides powerful elements for collecting user input, displaying tabular data, and embedding media. Proper use of <label> elements linked to inputs via the for attribute is essential - it makes forms usable for keyboard-only and screen-reader users. Similarly, tables should always include <thead> and <th scope> so assistive technologies can read column headers aloud.

<!-- Accessible form: every input has a linked label,
     required fields are marked, and inputs use the correct type
     so mobile keyboards show the right layout. -->
<form action="/register" method="POST">
  <fieldset>
    <legend>Create an Account</legend>

    <label for="email">Email address</label>
    <input type="email" id="email" name="email"
           placeholder="you@example.com" required>

    <label for="password">Password (min 8 characters)</label>
    <input type="password" id="password" name="password"
           minlength="8" required>

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

    <button type="submit">Register</button>
  </fieldset>
</form>

<!-- Accessible table with scope attributes -->
<table>
  <caption>Browser Market Share – 2025</caption>
  <thead>
    <tr>
      <th scope="col">Browser</th>
      <th scope="col">Share</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Chrome</td><td>65%</td></tr>
    <tr><td>Safari</td><td>19%</td></tr>
    <tr><td>Firefox</td><td>3%</td></tr>
  </tbody>
</table>

<!-- Responsive video embed -->
<video controls width="100%" poster="thumb.jpg">
  <source src="intro.mp4" type="video/mp4">
  Your browser does not support the video element.
</video>
science Interactive: Live Form with HTML5 Validation

Try filling out the form below. HTML5 validation attributes (required, type="email", minlength) provide built-in checking - no JavaScript needed for basic validation. Click Submit to test it, or type in the fields to see real-time feedback.

edit_note

Try It Yourself - Live HTML Editor

Interactive

Edit the HTML below and click Run to render it live. Try adding new elements like <img>, <table>, or <form> to see how the browser displays them. This is the fastest way to build intuition for how HTML tags translate into visible content.

science Interactive: Live HTML Editor

Write HTML in the editor and click Run to see it rendered live.

description

HTML Cheatsheet

Quick Reference

Document Skeleton

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

Common Elements

Element Purpose Type
<h1>–<h6>Headings (h1 largest)Block
<p>ParagraphBlock
<a href="">HyperlinkInline
<img src="" alt="">Image (self-closing)Inline
<ul> / <ol> / <li>Unordered / ordered listBlock
<div> / <span>Block / inline containerBlock / Inline
<strong> / <em>Important / emphasised textInline
<table> / <tr> / <td>Table / row / cellBlock
<form> / <input> / <button>Form / input field / buttonBlock / Inline
<br> / <hr>Line break / horizontal ruleSelf-closing

Semantic Landmarks

<header> <nav> <main> <article> <section> <aside> <footer> <figure>

Key Attributes

id class href src / alt target="_blank" rel="noopener noreferrer" required loading="lazy" data-*

Forms

<form action="/submit" method="POST">
  <label for="email">Email</label>
  <input type="email" id="email" required>
  <input type="password" minlength="8">
  <select><option>Choice</option></select>
  <textarea rows="4"></textarea>
  <button type="submit">Send</button>
</form>

Entities

&lt; → < &gt; → > &amp; → & &nbsp; → (space) &copy; → ©
lightbulb

Key Takeaways

  • Always include <!DOCTYPE html>, charset, and viewport meta tags - they prevent layout and encoding bugs.
  • Choose semantic elements (<nav>, <main>, <article>) over generic <div> tags for better accessibility and SEO.
  • Understand block vs inline: block elements stack and accept width/height; inline elements flow within text and ignore vertical margin.
  • Use correct attributes: alt on every image, rel="noopener noreferrer" on external target="_blank" links, and loading="lazy" for off-screen images.
  • Link every <label> to its input with the for attribute - this is not optional for accessible forms.
  • Use <thead> and <th scope> in tables so screen readers can announce column headers.
  • Use HTML5 validation attributes (required, type="email", minlength) for client-side form validation before JavaScript is even needed.
  • Validate your HTML using the W3C validator to catch errors early.
flag

Learning Objectives

By the end of this section you will be able to:

  • Write CSS rules using element, class, ID, and combinatory selectors.
  • Explain the box model and why box-sizing: border-box prevents layout surprises.
  • Use Flexbox and Grid to build responsive, modern layouts.
  • Understand the cascade and specificity to debug style conflicts.
  • Use position (relative, absolute, fixed, sticky) to place elements precisely.
  • Build mobile-first responsive designs using media queries.
  • Choose the right CSS units (rem, em, %, vh/vw) for scalable designs.
  • Add smooth transitions, keyframe animations, and CSS custom properties for theming.
palette

What is CSS?

CSS

CSS (Cascading Style Sheets) controls the visual presentation of HTML elements - colours, fonts, spacing, layout, and animations. By separating content (HTML) from presentation (CSS), you can completely redesign a website without touching any HTML. This separation is a core principle of web development because it makes sites easier to maintain, faster to update, and more accessible.

Why does this matter? Imagine a site with 50 pages. Without CSS, every visual change means editing all 50 HTML files. With one shared stylesheet, a single line change - say, updating a brand colour - propagates instantly across the entire site.

  • Selectors target which HTML elements to style (e.g., p, .card, #hero).
  • Properties define what to change (e.g., color, font-size, margin).
  • Values specify the desired setting (e.g., red, 1.5rem, 24px).
  • The cascade determines which rule wins when multiple rules target the same element - specificity and source order matter.
science Interactive: CSS On / Off - See the Difference

Toggle CSS on and off to see how the same HTML looks with and without styling. This demonstrates exactly what CSS does - it transforms raw, unstyled content into a polished, readable layout.

colorize

Colours - RGB, HEX & HSL

CSS

CSS supports multiple colour formats. Named colours (red, teal) are readable but limited to ~140 options. HEX (#1a6b52) is the most common format - 6 hex digits representing red, green, and blue. RGB (rgb(26, 107, 82)) uses decimal values 0–255 for each channel. HSL (hsl(155, 61%, 26%)) defines hue (0–360°), saturation (0–100%), and lightness (0–100%) - it is the most intuitive for choosing colour variations because you can adjust lightness and saturation independently. All three formats also support an alpha channel for transparency (e.g., rgba(), hsla(), or 8-digit hex).

/* Named colours (limited but readable) */
color: red;
color: teal;
color: cornflowerblue;

/* HEX  -  #RRGGBB (most common) */
color: #1a6b52;          /* 6-digit hex */
color: #1a6b52cc;        /* 8-digit hex with alpha (80% opacity) */

/* RGB  -  red, green, blue (0–255) */
color: rgb(26, 107, 82);
color: rgba(26, 107, 82, 0.8);   /* with alpha */

/* HSL  -  hue (0–360), saturation (%), lightness (%) */
color: hsl(155, 61%, 26%);
color: hsla(155, 61%, 26%, 0.8); /* with alpha */
text_fields

Typography - Fonts, Size & Spacing

CSS

Typography controls how text looks and reads. The key properties are font-family (the typeface), font-size (how big), font-weight (how bold), line-height (vertical spacing between lines), text-align (left, center, right, justify), and text-decoration (underline, strikethrough). Good typography makes content readable and scannable - a page with 1.6 line-height and proper heading sizes is dramatically easier to read than one with default spacing.

/* Font stack  -  fallback chain from specific to generic */
body {
  font-family: "Outfit", "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;           /* 16px default */
  font-weight: 400;          /* Normal weight */
  line-height: 1.6;          /* 1.6x the font size  -  optimal for readability */
  color: #333;
}

h1 {
  font-size: 2.5rem;         /* 40px */
  font-weight: 700;          /* Bold */
  line-height: 1.2;          /* Tighter for headings */
  letter-spacing: -0.5px;    /* Slightly tighter tracking */
}

a {
  text-decoration: none;     /* Remove default underline */
  color: teal;
}

a:hover {
  text-decoration: underline; /* Underline on hover for clarity */
}

.caption {
  font-size: 0.75rem;
  font-style: italic;
  text-transform: uppercase;  /* ALL CAPS */
  letter-spacing: 1px;
}
gradient

Backgrounds & Gradients

CSS

CSS backgrounds go far beyond solid colours. You can apply images, gradients, and even multiple layered backgrounds to any element. Linear gradients transition between colours along a straight line. Radial gradients radiate from a center point. Combining backgrounds with background-size, background-position, and background-repeat gives you full control over how images and patterns fill their container.

/* Solid colour */
background-color: #f0f4f8;

/* Linear gradient  -  direction + colour stops */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

/* Radial gradient  -  centre outward */
background: radial-gradient(circle, #ffecd2 0%, #fcb69f 100%);

/* Background image with cover sizing */
background-image: url("hero.jpg");
background-size: cover;       /* Scale to fill container */
background-position: center;  /* Centre the image */
background-repeat: no-repeat;

/* Multiple backgrounds (layered, first on top) */
background:
  linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)),
  url("hero.jpg") center / cover no-repeat;
tune

Selectors & Specificity

CSS

CSS offers many ways to target elements. Understanding specificity - the system that decides which rule wins when two rules compete - is crucial. Specificity is calculated as a three-part score: IDs > Classes > Elements. The higher the score, the more weight a rule carries. Knowing this saves hours of frustration wondering "why isn't my style applying?"

/* ===== ELEMENT SELECTOR (specificity 0-0-1) =====
   Targets every <p> on the page.
   Low specificity  -  easy to override. */
p {
  color: #333;
  line-height: 1.6;
}

/* ===== CLASS SELECTOR (specificity 0-1-0) =====
   Targets any element with class="highlight".
   Best practice: prefer classes over IDs for styling
   because they are reusable across many elements. */
.highlight {
  background-color: #fff3cd;
  padding: 4px 8px;
  border-radius: 4px;
}

/* ===== ID SELECTOR (specificity 1-0-0) =====
   Targets the ONE element with id="page-title".
   Very high specificity  -  hard to override later.
   Best practice: avoid IDs for styling; use them for
   JS hooks and anchor links instead. */
#page-title {
  font-size: 2rem;
  font-weight: bold;
}

/* ===== COMBINATORS =====
   Descendant selector: only <a> tags inside <nav> */
nav a {
  text-decoration: none;
  color: teal;
  transition: color 0.2s ease;
}

/* Pseudo-class: style on hover state */
nav a:hover {
  color: darkcyan;
  text-decoration: underline;
}

/* ===== SPECIFICITY IN ACTION =====
   Both rules target the same element:
   .card p       → specificity 0-1-1
   p.intro       → specificity 0-1-1  (tie → last one wins)
   #hero .card p → specificity 1-1-1  (highest → always wins) */
check_box_outline_blank

The Box Model

Core Concept

Every HTML element is rendered as a rectangular box with four layers: content (the text or image), padding (space between content and border), border (the visible edge), and margin (space outside the border separating elements).

Why box-sizing: border-box matters: By default, width only controls the content area - padding and border are added on top, making elements wider than expected. Setting box-sizing: border-box includes padding and border within the declared width, which makes layout calculations predictable. Most modern CSS resets apply this globally.

/* Apply border-box globally  -  this is considered
   best practice in every modern project. */
*, *::before, *::after {
  box-sizing: border-box;
}

.card {
  width: 300px;        /* Total width = 300px (including padding + border) */
  padding: 20px;       /* Inner spacing */
  border: 2px solid #ccc;
  margin: 16px auto;   /* 16px vertical gap; auto centres horizontally */
  border-radius: 12px; /* Rounded corners */
}
deployed_code Interactive Box Model Visualiser

Use the buttons to adjust padding and margin on the box below. Watch how each layer of the box model grows and shrinks in real time.

margin
padding
Content Area

padding: 20px; margin: 16px;

grid_view

Layout with Flexbox & Grid

CSS

Before Flexbox and Grid, developers used floats and absolute positioning to build layouts - techniques that were fragile and hard to maintain. Flexbox handles one-dimensional layouts (rows or columns), while CSS Grid handles two-dimensional layouts (rows and columns simultaneously). Choosing the right tool means your layouts will be responsive, readable, and far easier to debug.

/* ===== FLEXBOX: Navigation bar =====
   Items flow in a row, centred vertically,
   with equal spacing between them. */
.navbar {
  display: flex;
  align-items: center;      /* vertical centre */
  justify-content: space-between;
  padding: 12px 24px;
  gap: 16px;                /* space between flex children */
}

/* Make the nav responsive: stack vertically on small screens */
@media (max-width: 600px) {
  .navbar {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ===== CSS GRID: Card gallery =====
   Auto-fill columns that are at least 280px wide.
   The grid adapts from 1 to 3+ columns automatically. */
.card-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
  padding: 24px;
}

/* Span a featured card across two columns on wide screens */
.card-gallery .featured {
  grid-column: span 2;
}
science Interactive: Flexbox Playground

Change the Flexbox properties below and watch how the child items rearrange in real time. This is the fastest way to build intuition for justify-content, align-items, and flex-direction.

A
B
C
D
open_with

CSS Positioning

CSS

The position property controls how an element is placed within the document flow. Understanding the five positioning modes is essential for building overlays, sticky headers, tooltips, and any layout where elements need to overlap or break out of normal flow.

  • static - the default. Elements follow normal document flow. top/left/right/bottom have no effect.
  • relative - the element stays in normal flow but can be offset from its original position. Crucially, it creates a positioning context for absolute children.
  • absolute - the element is removed from flow and positioned relative to its nearest positioned ancestor (not static). If none exists, it positions relative to the <html> element.
  • fixed - removed from flow and positioned relative to the viewport. It stays in place even when the user scrolls (used for sticky navbars and floating buttons).
  • sticky - a hybrid: the element scrolls normally until it reaches a specified offset, then "sticks" in place. Useful for table headers and section labels.
/* ===== RELATIVE + ABSOLUTE: Parent-child positioning =====
   The parent is 'relative' so the child's 'absolute'
   position is calculated from the parent, not the page. */
.tooltip-wrapper {
  position: relative;   /* Creates positioning context */
}

.tooltip {
  position: absolute;
  bottom: 100%;         /* Place above the parent */
  left: 50%;
  transform: translateX(-50%);  /* Centre horizontally */
  background: #333;
  color: #fff;
  padding: 8px 12px;
  border-radius: 6px;
  white-space: nowrap;
  display: none;        /* Hidden by default */
}

.tooltip-wrapper:hover .tooltip {
  display: block;       /* Show on hover */
}

/* ===== FIXED: Sticky navigation bar =====
   Stays at the top of the viewport during scroll. */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;       /* Ensure it overlaps other content */
  background: white;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* ===== STICKY: Section header sticks while scrolling =====
   Scrolls normally until 0px from the top, then sticks. */
.section-header {
  position: sticky;
  top: 0;
  background: white;
  padding: 12px 0;
  z-index: 10;
}
science Interactive: Position Mode Playground

Change the position mode of the target box below. The grey parent has position: relative so it acts as the positioning context. Adjust the top and left offsets with the sliders.

position
top: 20px
left: 20px
parent (position: relative)
Sibling before
Target Box
Sibling after
devices

Responsive Design & Media Queries

CSS

Responsive design ensures your website looks and works well on every screen size - from phones to ultra-wide monitors. The key tool is the @media rule, which lets you apply CSS conditionally based on viewport width, orientation, or other features. Combined with fluid layouts (Flexbox, Grid, percentage widths) and the viewport meta tag in HTML, media queries let you build a single codebase that adapts to all devices.

Mobile-first approach: write your base CSS for small screens, then use min-width media queries to add complexity for larger screens. This is the industry standard because it keeps the base styles simple and loads less CSS on slower mobile connections.

/* ===== MOBILE-FIRST: Base styles (phones, <600px) ===== */
.container {
  padding: 16px;
  max-width: 100%;
}

.card-grid {
  display: grid;
  grid-template-columns: 1fr;  /* Single column on mobile */
  gap: 16px;
}

/* ===== TABLET: 600px and up ===== */
@media (min-width: 600px) {
  .container {
    padding: 24px;
    max-width: 720px;
    margin: 0 auto;
  }

  .card-grid {
    grid-template-columns: repeat(2, 1fr);  /* Two columns */
  }
}

/* ===== DESKTOP: 1024px and up ===== */
@media (min-width: 1024px) {
  .container {
    padding: 32px;
    max-width: 960px;
  }

  .card-grid {
    grid-template-columns: repeat(3, 1fr);  /* Three columns */
    gap: 24px;
  }
}

/* ===== ORIENTATION & PREFERENCE QUERIES ===== */
@media (orientation: landscape) and (max-height: 500px) {
  /* Compact layout for landscape phones */
  .hero { padding: 16px; }
}

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

@media (prefers-reduced-motion: reduce) {
  /* Disable animations for users who prefer reduced motion */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
straighten

CSS Units - When to Use What

CSS

Choosing the right CSS unit is important for building layouts that scale correctly across devices and respect user preferences. A common mistake is using px for everything - this ignores the user's browser font-size settings and creates rigid layouts that don't adapt.

  • px - absolute pixels. Use for borders, shadows, and small fixed details. Avoid for font sizes and layout widths.
  • rem - relative to the root (<html>) font size (usually 16px). Best for font sizes, spacing, and component sizing because it scales with user preferences.
  • em - relative to the parent element's font size. Useful for padding/margin that should scale with text, but compounds in nested elements (1.2em inside 1.2em = 1.44× the root).
  • % - relative to the parent's dimension. Ideal for fluid widths (width: 100%) and responsive images.
  • vh / vw - 1% of viewport height/width. Great for full-screen sections (min-height: 100vh) and hero banners.
  • ch - width of the "0" character. Perfect for setting readable line lengths (max-width: 65ch).
/* Best practice: use rem for type and spacing, % or viewport
   units for layout, and px only for fine details. */

html {
  font-size: 16px;        /* 1rem = 16px (browser default) */
}

body {
  font-size: 1rem;        /* 16px  -  scales with user settings */
  line-height: 1.6;       /* Unitless  -  multiplier of font-size */
}

h1 { font-size: 2.5rem; }   /* 40px at default, but scales */
h2 { font-size: 1.75rem; }  /* 28px at default */

.container {
  max-width: 65ch;        /* ~65 characters wide  -  optimal readability */
  padding: 2rem;          /* Scales with root font size */
  margin: 0 auto;
}

.hero {
  min-height: 100vh;      /* Full viewport height */
  padding: 5vw;           /* Responsive padding */
}

.border-detail {
  border: 1px solid #ccc; /* px is fine for thin borders */
  box-shadow: 0 2px 8px rgba(0,0,0,0.12); /* px for shadows */
}
animation

Transitions, Animations & Custom Properties

CSS

Transitions smoothly animate property changes between two states (e.g., hover effects). Keyframe animations allow multi-step, repeating, or auto-playing sequences. Both are performant because the browser's compositor handles them - they run at 60fps without blocking JavaScript.

CSS custom properties (variables) let you define reusable values with --name: value and reference them with var(--name). They cascade like regular properties and can be changed dynamically with JavaScript, making them perfect for theming and design tokens.

/* ===== CSS CUSTOM PROPERTIES (VARIABLES) =====
   Define once in :root, reuse everywhere. */
:root {
  --color-primary: #1a6b52;
  --color-surface: #f8faf6;
  --radius-md: 12px;
  --transition-standard: 300ms cubic-bezier(0.2, 0, 0, 1);
}

/* ===== TRANSITIONS =====
   Smoothly animate between two states (e.g., default → hover).
   Only animate transform and opacity for best performance. */
.btn {
  background-color: var(--color-primary);
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  /* Transition multiple properties */
  transition: transform var(--transition-standard),
              box-shadow var(--transition-standard);
}

.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

.btn:active {
  transform: translateY(0);
}

/* ===== KEYFRAME ANIMATIONS =====
   Multi-step animations that can loop and play automatically. */
@keyframes fadeSlideIn {
  0%   { opacity: 0; transform: translateY(20px); }
  100% { opacity: 1; transform: translateY(0); }
}

.card {
  animation: fadeSlideIn 0.5s ease-out forwards;
}

/* Stagger cards: each child animates slightly later */
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }
.card:nth-child(4) { animation-delay: 0.3s; }

/* ===== RESPECT USER PREFERENCES ===== */
@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
  .btn  { transition: none; }
}
edit_note

Try It Yourself - Live CSS Editor

Interactive

Write CSS rules in the editor and click Run to apply them to the preview below. Try changing color, font-family, display: flex, or border-radius. Notice how the styles scope only to the preview area without affecting the rest of the page.

science Interactive: Live CSS Editor

Edit the CSS and click Run to see it applied to the sample HTML below.

Show sample HTML (what you are styling)
palette

CSS Cheatsheet

Quick Reference

Selectors

Selector Targets Specificity
pAll <p> elements0-0-1
.cardElements with class="card"0-1-0
#heroElement with id="hero"1-0-0
nav aAll <a> inside <nav>0-0-2
a:hoverLinks on hover0-1-1
*All elements (universal)0-0-0

Box Model

* { box-sizing: border-box; }   /* Include padding/border in width */

.box {
  width: 300px;   padding: 16px;
  border: 2px solid #ccc;   margin: 24px auto;
}

Layout

/* Flexbox (one-dimensional) */
display: flex;   flex-direction: row | column;
justify-content: flex-start | center | space-between;
align-items: stretch | center | flex-start;
gap: 16px;

/* Grid (two-dimensional) */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;

Positioning

staticDefault - normal flow, top/left ignored
relativeOffset from normal position; creates context for absolute children
absoluteRemoved from flow; relative to nearest positioned ancestor
fixedRelative to viewport; stays during scroll
stickyNormal flow until offset reached, then sticks

Units

px - absolute pixels rem - relative to root font-size em - relative to parent font-size % - relative to parent dimension vh / vw - viewport height / width ch - width of "0" character

Responsive

/* Mobile-first: base styles for phones, then add complexity */
@media (min-width: 600px) { /* tablet */ }
@media (min-width: 1024px) { /* desktop */ }
@media (prefers-color-scheme: dark) { /* dark mode */ }
@media (prefers-reduced-motion: reduce) { /* a11y */ }

Colours, Transitions & Variables

/* Colour formats */
color: #1a6b52;                      /* HEX */
color: rgb(26, 107, 82);             /* RGB */
color: hsl(155, 61%, 26%);           /* HSL */
color: rgba(26, 107, 82, 0.8);      /* with alpha */

/* Custom properties */
:root { --primary: #1a6b52; }
.btn  { color: var(--primary); }

/* Transition */
transition: transform 300ms ease, opacity 300ms ease;

/* Keyframe animation */
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
.el { animation: fadeIn 0.5s ease forwards; }
lightbulb

Key Takeaways

  • Prefer class selectors over IDs for styling - they are reusable and have lower specificity, making overrides easier.
  • Always set box-sizing: border-box globally to avoid unexpected width calculations.
  • Use Flexbox for one-dimensional flows (navigation bars, centering) and Grid for two-dimensional layouts (card galleries, dashboards).
  • Understand position - use relative as a container for absolute children, fixed for viewport-locked elements, and sticky for scroll-aware headers.
  • Build mobile-first: write base styles for small screens, then add complexity with @media (min-width: ...) queries.
  • Use rem for font sizes and spacing, % or vw/vh for layout, and px only for fine details like borders.
  • Define reusable values with CSS custom properties (--name: value) and always respect prefers-reduced-motion for accessibility.
  • When a style "isn't working," check specificity first - a more specific rule elsewhere is probably winning the cascade.
flag

Learning Objectives

By the end of this section you will be able to:

  • Declare variables with const and let and explain why var is avoided.
  • Write functions (including arrow functions) and understand scope.
  • Use conditionals (if/else, switch, ternary) and loops (for, for...of, while).
  • Work with objects, destructuring, the spread operator, and template literals.
  • Select and modify DOM elements using both vanilla JS and jQuery.
  • Handle user events (click, input, submit) to build interactive UIs.
  • Handle errors gracefully with try/catch and throw custom errors.
  • Use Promises, fetch(), and async/await for asynchronous operations.
bolt

What is JavaScript?

JavaScript

JavaScript (JS) is the programming language of the web. While HTML defines what content exists and CSS defines how it looks, JavaScript defines what happens when users interact with the page - clicking buttons, submitting forms, scrolling, typing, or receiving new data from a server.

Why does this matter? Without JavaScript, web pages are static documents. JS transforms them into applications - think of Google Maps, Spotify Web Player, or online banking. It is the only programming language that runs natively in every web browser, making it an essential skill for any web developer.

  • JS runs in the browser's JavaScript engine (e.g., V8 in Chrome) - no compilation step needed.
  • It is dynamically typed: variables can hold any data type without a type declaration.
  • Event-driven: code executes in response to user actions or system events, not just top-to-bottom.
  • The DOM API lets JS read and modify every element on the page in real time.
data_object

Variables, Scope & Functions

JavaScript

Modern JavaScript uses const (for values that never change) and let (for values that need reassignment). The older var keyword is avoided because it is function-scoped rather than block-scoped, which leads to subtle bugs in loops and conditionals. This is one of the most important best practices in JS.

// ===== VARIABLES & SCOPE =====

const TAX_RATE = 0.1;    // const – value never changes
let   cartTotal = 0;      // let – will be updated later

// var is function-scoped  -  DO NOT use in modern code:
// for (var i = 0; i < 3; i++) { ... }
// console.log(i); // 3  ← leaked outside the loop!

// let is block-scoped  -  stays inside its block:
// for (let j = 0; j < 3; j++) { ... }
// console.log(j); // ReferenceError ← correct behaviour

// ===== FUNCTIONS =====

// Traditional function declaration
function calculateTotal(price, quantity) {
  const subtotal = price * quantity;
  const tax = subtotal * TAX_RATE;
  return subtotal + tax;
}

// Arrow function (concise ES6+ syntax)
const formatCurrency = (amount) => `$${amount.toFixed(2)}`;

// Using both together
const total = calculateTotal(29.99, 3);
console.log(formatCurrency(total));  // "$98.97"

// ===== ARRAYS & HIGHER-ORDER FUNCTIONS =====

const products = [
  { name: "Laptop",  price: 999 },
  { name: "Mouse",   price: 29  },
  { name: "Monitor", price: 449 },
];

// .filter() returns a new array of items matching the condition
const affordable = products.filter(p => p.price < 500);

// .map() transforms each item into a new value
const names = products.map(p => p.name);

// .reduce() accumulates values into a single result
const totalPrice = products.reduce((sum, p) => sum + p.price, 0);

console.log(affordable);  // [{name:"Mouse",...}, {name:"Monitor",...}]
console.log(names);       // ["Laptop", "Mouse", "Monitor"]
console.log(totalPrice);  // 1477
alt_route

Control Flow - Conditionals & Loops

JavaScript

Control flow statements let your code make decisions (if/else, switch) and repeat actions (for, while, for...of). Without these, programs could only execute the same sequence every time. They are the backbone of any interactive feature - validating form input, filtering a list, processing quiz answers, or retrying a network request.

// ===== CONDITIONALS =====

const score = 82;

// if / else if / else  -  the most common branching pattern
if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 80) {
  console.log("Grade: B");   // ← this runs
} else if (score >= 70) {
  console.log("Grade: C");
} else {
  console.log("Grade: F");
}

// Ternary operator  -  concise for simple conditions
const status = score >= 60 ? "Pass" : "Fail";
console.log(status);  // "Pass"

// switch  -  cleaner when comparing one value against many options
const day = "Monday";
switch (day) {
  case "Monday":
  case "Tuesday":
  case "Wednesday":
  case "Thursday":
  case "Friday":
    console.log("Weekday");
    break;
  case "Saturday":
  case "Sunday":
    console.log("Weekend");
    break;
  default:
    console.log("Invalid day");
}

// ===== LOOPS =====

// for loop  -  when you know the number of iterations
for (let i = 0; i < 5; i++) {
  console.log("Iteration", i);   // 0, 1, 2, 3, 4
}

// for...of  -  iterate over array values (ES6+, preferred)
const colours = ["red", "green", "blue"];
for (const colour of colours) {
  console.log(colour);
}

// while  -  when the number of iterations is unknown
let attempts = 0;
while (attempts < 3) {
  console.log("Attempt", attempts + 1);
  attempts++;
}

// .forEach()  -  array method (functional style, no break/continue)
colours.forEach((colour, index) => {
  console.log(`${index + 1}. ${colour}`);
});
science Interactive: Grade Calculator (Conditionals in Action)

Enter a numeric score and click Calculate. The JavaScript behind this uses an if / else if / else chain - the exact same pattern from the code snippet above - to determine the letter grade in real time.

category

Objects, Data Types & Template Literals

JavaScript

JavaScript has primitive types (string, number, boolean, null, undefined, symbol, bigint) and one structural type: objects. Arrays, functions, dates, and regular expressions are all objects under the hood. Understanding data types prevents bugs like accidentally concatenating numbers as strings or comparing values with the wrong operator.

Template literals (backtick strings) let you embed expressions directly inside strings using ${...} syntax. They also support multi-line strings without escape characters - a significant improvement over traditional string concatenation.

// ===== DATA TYPES =====
const name   = "Alice";          // string
const age    = 28;               // number
const active = true;             // boolean
const score  = null;             // null (intentionally empty)
let   role;                      // undefined (not yet assigned)

// typeof checks the type at runtime
console.log(typeof name);    // "string"
console.log(typeof age);     // "number"
console.log(typeof active);  // "boolean"

// ⚠ Comparison: always use === (strict) instead of == (loose)
console.log(0 == "0");    // true  ← type coercion, dangerous!
console.log(0 === "0");   // false ← strict, no coercion, safe

// ===== OBJECTS =====
// Objects store key-value pairs (properties and methods)
const user = {
  firstName: "Alice",
  lastName: "Chen",
  age: 28,
  skills: ["HTML", "CSS", "JavaScript"],

  // Method: a function that belongs to the object
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
};

// Accessing properties
console.log(user.firstName);          // "Alice"
console.log(user["lastName"]);        // "Chen" (bracket notation)
console.log(user.getFullName());      // "Alice Chen"

// Destructuring: extract properties into variables
const { firstName, skills } = user;
console.log(firstName);   // "Alice"
console.log(skills[0]);   // "HTML"

// Spread operator: copy and extend objects
const updatedUser = { ...user, age: 29, role: "Developer" };

// ===== TEMPLATE LITERALS =====
// Use backticks (`) instead of quotes for embedded expressions
const greeting = `Hello, ${user.getFullName()}!
You have ${user.skills.length} skills listed.
Your profile is ${user.active !== false ? "active" : "inactive"}.`;

console.log(greeting);
// "Hello, Alice Chen!
//  You have 3 skills listed.
//  Your profile is active."
text_snippet

String Methods

JavaScript

Strings are one of the most commonly used data types. JavaScript provides dozens of built-in methods to search, extract, transform, and split strings. Since strings are immutable (they cannot be changed in place), every method returns a new string rather than modifying the original.

const msg = "  Hello, World!  ";

// Length (property, not a method)
msg.length;                    // 19

// Trimming whitespace
msg.trim();                    // "Hello, World!"

// Case conversion
msg.trim().toUpperCase();      // "HELLO, WORLD!"
msg.trim().toLowerCase();      // "hello, world!"

// Searching
msg.includes("World");         // true
msg.indexOf("World");          // 9  (position, or -1 if not found)
msg.startsWith("  Hello");     // true

// Extracting substrings
msg.trim().slice(0, 5);        // "Hello"
msg.trim().slice(7);           // "World!"

// Replacing
msg.trim().replace("World", "JavaScript");  // "Hello, JavaScript!"
msg.trim().replaceAll("l", "L");            // "HeLLo, WorLd!"

// Splitting into an array
"HTML,CSS,JS".split(",");      // ["HTML", "CSS", "JS"]
"Hello World".split(" ");      // ["Hello", "World"]

// Joining an array back into a string
["HTML", "CSS", "JS"].join(" & ");  // "HTML & CSS & JS"

// Padding
"5".padStart(3, "0");          // "005"

// Template literal with expressions
const name = "Alice";
`Hello, ${name.toUpperCase()}!`;  // "Hello, ALICE!"
account_tree

DOM Manipulation

JavaScript

The Document Object Model (DOM) is the browser's in-memory representation of the HTML page. JavaScript can select elements, change their content and styles, create new elements, and remove existing ones - all without reloading the page.

Best practice: minimise direct DOM manipulation. Each time you change the DOM, the browser may need to recalculate layout (called a "reflow"). Batch your changes or use DocumentFragment to avoid performance issues on complex pages.

// ===== SELECTING ELEMENTS =====
const title   = document.getElementById("main-title");
const cards   = document.querySelectorAll(".card");   // NodeList
const firstP  = document.querySelector("p");          // first match

// ===== MODIFYING CONTENT & STYLES =====
title.textContent = "Updated Title";
title.style.color = "#00695c";

// classList is safer than overwriting className directly
title.classList.add("highlight");
title.classList.toggle("hidden");

// ===== CREATING & APPENDING ELEMENTS =====
// Using DocumentFragment to batch-insert (avoids multiple reflows)
const fragment = document.createDocumentFragment();
const fruits = ["Apple", "Banana", "Cherry"];

fruits.forEach(fruit => {
  const li = document.createElement("li");
  li.textContent = fruit;
  li.classList.add("fruit-item");
  fragment.appendChild(li);
});

// Only ONE reflow when the fragment is appended
document.getElementById("fruit-list").appendChild(fragment);

// ===== EVENT LISTENERS =====
document.querySelector("#submitBtn")
  .addEventListener("click", function(event) {
    event.preventDefault();  // stop form from refreshing the page
    const input = document.querySelector("#nameInput");
    alert("Hello, " + input.value + "!");
  });
science Interactive: DOM Manipulation Playground

Use the buttons to add, style, rename, and remove elements in real time.

auto_awesome

jQuery - Simplified DOM & AJAX

jQuery

jQuery is a lightweight library that simplifies DOM manipulation, event handling, and AJAX calls. While modern vanilla JS has caught up in many areas, jQuery remains valuable for projects that need broad browser compatibility and concise syntax. Its $.ajax() method is particularly useful for loading content dynamically - a technique used on the Quiz page of this application.

// Wait for the DOM to be fully ready before running code
$(document).ready(function() {

  // ===== SELECTING & MODIFYING =====
  // jQuery wraps elements in a powerful object with chaining
  $("h1")
    .css("color", "#00695c")
    .addClass("animated")
    .attr("data-loaded", "true");

  // ===== EVENT HANDLING =====
  // .on() attaches events; supports delegation for dynamic content
  $("#quiz-container").on("click", ".answer-btn", function() {
    // 'this' refers to the clicked .answer-btn, even if it was
    // added to the DOM after page load (event delegation).
    $(this).addClass("selected");
    $(".answer-btn").not(this).removeClass("selected");
  });

  // ===== AJAX: Load quiz data from a JSON file =====
  $.ajax({
    url: "data/questions.json",
    method: "GET",
    dataType: "json",
    success: function(data) {
      // data is already parsed as a JS object
      console.log("Loaded " + data.length + " questions");
      renderQuiz(data);
    },
    error: function(xhr, status, err) {
      console.error("Failed to load quiz:", status, err);
      $("#quiz-container").html(
        "<p class='error'>Could not load questions. Please try again.</p>"
      );
    }
  });

  // ===== ANIMATION =====
  $(".card").hide().fadeIn(600);

});
bug_report

Error Handling - try / catch / finally

JavaScript

Errors are inevitable in real applications - users enter invalid data, network requests fail, and APIs return unexpected responses. The try...catch statement lets you gracefully handle errors instead of crashing the entire page. The optional finally block runs regardless of success or failure, making it ideal for cleanup tasks like hiding loading spinners.

// ===== BASIC TRY / CATCH =====

try {
  // Code that might throw an error
  const data = JSON.parse('{ invalid json }');
} catch (error) {
  // Runs only if an error occurred in the try block
  console.error("Parsing failed:", error.message);
  // "Parsing failed: Expected property name or '}' ..."
} finally {
  // Always runs  -  whether the try succeeded or failed
  console.log("Cleanup: hiding loading spinner");
}

// ===== THROWING CUSTOM ERRORS =====

function divide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("Both arguments must be numbers");
  }
  if (b === 0) {
    throw new RangeError("Cannot divide by zero");
  }
  return a / b;
}

try {
  const result = divide(10, 0);
} catch (err) {
  if (err instanceof RangeError) {
    console.warn("Division error:", err.message);
  } else {
    console.error("Unexpected error:", err);
  }
}

// ===== VALIDATING USER INPUT =====

function validateEmail(email) {
  if (!email || !email.includes("@")) {
    throw new Error("Please enter a valid email address");
  }
  return true;
}

try {
  validateEmail("not-an-email");
} catch (err) {
  // Display the error to the user in the UI
  document.getElementById("error-msg").textContent = err.message;
}
data_object

JSON - Data Exchange Format

JavaScript

JSON (JavaScript Object Notation) is the standard format for sending data between a server and a web browser. It looks almost identical to JavaScript object syntax, but with two key differences: all keys must be double-quoted strings, and values can only be strings, numbers, booleans, null, arrays, or nested objects (no functions, undefined, or comments). JSON.parse() converts a JSON string into a JavaScript object, and JSON.stringify() does the reverse.

// ===== JSON format (what you receive from an API) =====
// Keys MUST be double-quoted. No trailing commas.
const jsonString = `{
  "name": "Alice",
  "age": 28,
  "active": true,
  "skills": ["HTML", "CSS", "JavaScript"],
  "address": {
    "city": "Perth",
    "country": "Australia"
  }
}`;

// ===== JSON.parse()  -  String → Object =====
const user = JSON.parse(jsonString);
console.log(user.name);        // "Alice"
console.log(user.skills[0]);   // "HTML"
console.log(user.address.city);// "Perth"

// ===== JSON.stringify()  -  Object → String =====
const obj = { name: "Bob", score: 95 };
const str = JSON.stringify(obj);
console.log(str);  // '{"name":"Bob","score":95}'

// Pretty-print with indentation (useful for debugging)
console.log(JSON.stringify(obj, null, 2));

// ===== Common use: localStorage =====
// localStorage only stores strings  -  use JSON to save objects
localStorage.setItem("user", JSON.stringify(user));
const loaded = JSON.parse(localStorage.getItem("user"));
sync

Async JavaScript - Promises, fetch & async/await

JavaScript

JavaScript is single-threaded - it can only execute one thing at a time. To avoid freezing the UI while waiting for slow operations (network requests, file reads, timers), JS uses asynchronous patterns. A Promise represents a value that will be available in the future. The modern async/await syntax makes asynchronous code read like synchronous code, which is much easier to understand and debug.

The fetch() API is the modern replacement for XMLHttpRequest (and jQuery's $.ajax). It returns a Promise and supports a clean, chainable syntax.

// ===== PROMISES =====
// A Promise is either: pending, fulfilled, or rejected.

const myPromise = new Promise((resolve, reject) => {
  const success = true;
  setTimeout(() => {
    if (success) {
      resolve("Data loaded!");   // fulfilled
    } else {
      reject("Network error");   // rejected
    }
  }, 1000);
});

// Consuming a promise with .then() and .catch()
myPromise
  .then(data => console.log(data))     // "Data loaded!"
  .catch(err => console.error(err))
  .finally(() => console.log("Done")); // Always runs

// ===== FETCH API =====
// fetch() returns a Promise  -  the modern way to make HTTP requests.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    return response.json();  // Parse the JSON body (also a Promise)
  })
  .then(post => {
    console.log("Title:", post.title);
  })
  .catch(error => {
    console.error("Fetch failed:", error.message);
  });

// ===== ASYNC / AWAIT =====
// Syntactic sugar over Promises  -  reads top-to-bottom like sync code.

async function loadUser(userId) {
  try {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );

    if (!response.ok) {
      throw new Error(`User not found (HTTP ${response.status})`);
    }

    const user = await response.json();
    console.log(`Loaded: ${user.name} (${user.email})`);
    return user;

  } catch (error) {
    console.error("Failed to load user:", error.message);
    return null;
  }
}

// Call the async function
loadUser(1);  // Logs: "Loaded: Leanne Graham (Sincere@april.biz)"
edit_note

Try It Yourself - JavaScript Sandbox

Interactive

Write JavaScript below and click Run to execute it. Use output() instead of console.log() to display results in the output panel. Try using .filter(), .map(), or .reduce() on arrays!

science Interactive: JavaScript Sandbox

Write JavaScript and click Run. Use output() instead of console.log() to see results.

bug_report

Debugging with Browser DevTools

JavaScript

Every modern browser includes Developer Tools (DevTools) that let you inspect HTML, debug JavaScript, monitor network requests, and profile performance. Knowing how to use DevTools is one of the most important practical skills a web developer can have - it turns hours of guessing into minutes of targeted diagnosis.

The Console is your primary debugging tool. console.log() prints values for inspection, console.error() highlights problems, and console.table() formats arrays and objects as tables. The Elements panel lets you inspect and live-edit HTML and CSS. The Sources panel lets you set breakpoints and step through JavaScript line by line.

// ===== CONSOLE METHODS =====

console.log("Simple message");
console.log("User:", user);           // Inspect objects

console.error("Something went wrong"); // Red error styling
console.warn("Check this value");      // Yellow warning

// Table format for arrays/objects
console.table([
  { name: "Alice", score: 95 },
  { name: "Bob", score: 82 }
]);

// Group related logs together
console.group("Quiz Results");
console.log("Score: 12/15");
console.log("Percentage: 80%");
console.groupEnd();

// Measure execution time
console.time("AJAX load");
// ... some operation ...
console.timeEnd("AJAX load"); // "AJAX load: 245ms"

// ===== BREAKPOINTS =====
// In the Sources panel:
// 1. Click a line number to set a breakpoint
// 2. Reload or trigger the code path
// 3. Execution pauses at that line
// 4. Hover variables to see their current values
// 5. Use Step Over (F10), Step Into (F11), Continue (F8)

// Or use the debugger statement in code:
function calculateGrade(score) {
  debugger; // Execution pauses here when DevTools is open
  if (score >= 90) return "A";
  if (score >= 80) return "B";
  return "F";
}
  • Open DevTools: Press F12 or Ctrl+Shift+I (Windows/Linux) / Cmd+Option+I (Mac).
  • Console tab: Run JavaScript live, inspect variables, and see errors.
  • Elements tab: Inspect any element, live-edit CSS, and see computed styles.
  • Network tab: Monitor AJAX requests, check response status codes, and measure load times.
  • Application tab: Inspect localStorage, cookies, and sessionStorage.
touch_app

Event-Driven Programming

JavaScript

JavaScript is fundamentally event-driven: instead of executing code from top to bottom in a fixed sequence, you register listeners that wait for specific events to occur - a click, a keystroke, a form submission, a page load, or data arriving from a server. When the event fires, the browser calls your listener function. This model is what makes web pages interactive.

The event loop is the mechanism behind this. JavaScript runs a single thread that continuously checks a queue of pending events. When your code finishes executing, the engine picks up the next event in the queue and runs its handler. This is why long-running synchronous code (like a heavy loop) freezes the UI - it blocks the event loop from processing other events like clicks or scrolls.

// ===== EVENT LISTENERS =====
// Register a function that runs when a specific event occurs.
// The browser calls it automatically - you don't call it yourself.

const btn = document.querySelector("#submitBtn");

// addEventListener(eventType, handlerFunction)
btn.addEventListener("click", function(event) {
  event.preventDefault();   // Stop the default browser action
  console.log("Button clicked!");
  console.log("Target element:", event.target);
});

// ===== COMMON EVENT TYPES =====
// Mouse:    click, dblclick, mouseenter, mouseleave
// Keyboard: keydown, keyup, keypress
// Form:     submit, change, input, focus, blur
// Window:   load, resize, scroll, beforeunload
// Touch:    touchstart, touchend, touchmove

// ===== EVENT DELEGATION =====
// Instead of attaching a listener to every child element,
// attach ONE listener to the parent. The event "bubbles up"
// from the clicked child to the parent. This is more efficient
// and works with dynamically added elements.

document.querySelector("#quiz-container")
  .addEventListener("click", function(event) {
    // event.target is the actual element that was clicked
    if (event.target.classList.contains("answer-btn")) {
      event.target.classList.add("selected");
    }
  });

// ===== jQuery EQUIVALENT =====
// .on() supports delegation via a second selector argument:
$("#quiz-container").on("click", ".answer-btn", function() {
  $(this).addClass("selected");
  // 'this' is the .answer-btn that was clicked,
  // even if it was added to the DOM after page load.
});

// ===== THE EVENT OBJECT =====
// Every handler receives an Event object with useful properties:
document.addEventListener("keydown", function(e) {
  console.log("Key pressed:", e.key);     // "Enter", "a", "Escape"
  console.log("Ctrl held?", e.ctrlKey);   // true/false
  console.log("Target:", e.target);       // the focused element
  
  if (e.key === "Escape") {
    closeModal();
  }
});
  • Event bubbling: Events propagate from the target element up through its ancestors. This is why event delegation works - a click on a child <button> also triggers click handlers on its parent <div>, <body>, etc.
  • event.preventDefault(): Stops the browser's default action (e.g., prevents a form from reloading the page on submit, or a link from navigating).
  • event.stopPropagation(): Stops the event from bubbling further up the DOM tree. Use sparingly - it can break other listeners that depend on bubbling.
  • Prefer .addEventListener() over onclick: addEventListener allows multiple listeners on the same element and keeps JS separate from HTML. Inline onclick attributes mix concerns and only allow one handler.
  • Event delegation for performance: Instead of 100 listeners on 100 list items, attach one listener to the <ul> parent and check event.target. This uses less memory and automatically works with dynamically added items.
bolt

JavaScript Cheatsheet

Quick Reference

Variables & Types

const name = "Alice";    // Cannot reassign (preferred default)
let   count = 0;         // Can reassign
// var  -  avoid (function-scoped, leaks out of blocks)

// Primitives: string, number, boolean, null, undefined, symbol, bigint
typeof "hello"  // "string"
typeof 42       // "number"
typeof true     // "boolean"

// Always use === (strict) instead of == (loose coercion)

Functions

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

// Arrow function (ES6+)
const add = (a, b) => a + b;

// Default parameters
const greet = (name = "World") => `Hello, ${name}!`;

Control Flow

// Conditionals
if (x > 10) { } else if (x > 5) { } else { }
const result = x > 0 ? "positive" : "negative";  // ternary

// Loops
for (let i = 0; i < 5; i++) { }
for (const item of array) { }
while (condition) { }
array.forEach((item, index) => { });

Arrays

.push(x) / .pop()Add to end / remove from end
.filter(fn)New array with items passing test
.map(fn)New array with transformed items
.reduce(fn, init)Accumulate into single value
.find(fn) / .findIndex(fn)First match / its index
.includes(x)Check if array contains value
.sort(fn)Sort in place (mutates!)
.slice(a, b)Copy portion (non-destructive)
.join(sep)Array → string

Strings

.lengthCharacter count (property)
.trim()Remove leading/trailing whitespace
.toUpperCase() / .toLowerCase()Case conversion
.includes(s) / .indexOf(s)Search (boolean / position)
.slice(a, b)Extract substring
.replace(a, b)Replace first match
.split(sep)String → array
`${expr}`Template literal (backticks)

Objects & JSON

const user = { name: "Alice", age: 28 };
user.name;                      // Dot notation
user["name"];                   // Bracket notation
const { name, age } = user;    // Destructuring
const copy = { ...user, role: "Dev" };  // Spread

JSON.parse(jsonString);         // String → Object
JSON.stringify(obj);            // Object → String

DOM & Events

document.getElementById("id");
document.querySelector(".class");
document.querySelectorAll("p");

el.textContent = "new text";
el.classList.add("active");
el.style.color = "red";
el.remove();

// Events
el.addEventListener("click", (e) => {
  e.preventDefault();      // Stop default action
  e.stopPropagation();     // Stop bubbling
});

// Event delegation (one listener on parent)
parent.addEventListener("click", (e) => {
  if (e.target.matches(".child")) { /* handle */ }
});

// jQuery equivalents
$("selector").on("click", function() { });
$("parent").on("click", ".child", function() { }); // delegation

Debugging

console.log("value:", x);       // Inspect values
console.error("failed!");       // Red error
console.table(array);           // Table format
console.time("label");          // Start timer
console.timeEnd("label");       // End timer
debugger;                       // Breakpoint in code

// DevTools: F12 or Ctrl+Shift+I
// Console > Elements > Sources > Network > Application

Async & Error Handling

// try/catch
try { riskyOperation(); }
catch (err) { console.error(err.message); }
finally { cleanup(); }

// fetch + async/await
async function loadData() {
  const res = await fetch("url");
  const data = await res.json();
  return data;
}

// jQuery AJAX
$.ajax({ url: "data.json", method: "GET",
  success: function(data) { },
  error: function(err) { }
});
lightbulb

Key Takeaways

  • Always use const by default; switch to let only when reassignment is necessary. Never use var.
  • Always use === (strict equality) instead of == to avoid type coercion bugs.
  • Prefer for...of for iterating arrays and .forEach()/.map()/.filter() for functional transformations.
  • Use .addEventListener() (or jQuery's .on()) instead of inline onclick attributes - it keeps HTML and JS separated and supports multiple listeners.
  • Minimise DOM updates by batching changes with DocumentFragment or building HTML strings before inserting.
  • Always wrap risky operations (JSON parsing, network calls, user input) in try/catch to prevent unhandled crashes.
  • Use async/await with fetch() for clean, readable asynchronous code - it is the modern replacement for callback-based patterns and $.ajax().
  • Prefer event delegation (attach one listener to a parent) over attaching listeners to each child - it is more memory-efficient and works with dynamically added elements.
  • Use DevTools (F12) to debug: console.log() for inspecting values, the Sources panel for breakpoints, and the Network tab for monitoring AJAX requests.

Ready to Test Your Knowledge?

You've covered HTML structure, CSS styling, and JavaScript interactivity. Head to the quiz and put what you've learned to the test!

arrow_forward Take the Quiz